天天看點

切Bank card取number1. 洗菜2. 切菜不是尾巴的尾巴

怪異的标題!!!這道菜不難做,首先列舉一下本次用到的食材:

  • OpenCV v3.1
  • 一個銀行卡圖檔(四個邊的都要在圖像裡)

爐竈采用 visual studio 2013

1. 洗菜

資料預處理階段( ̄▽ ̄)”代碼更有說服力。

#include <iostream>
#include <opencv2/opencv.hpp>

using namespace std;
using namespace cv;

Point CrossPoint(const Vec4i& l1, const Vec4i& l2)
{

    float k1 = (float)(l1[] - l1[]) / (l1[] - l1[]);
    float b1 = l1[] - k1 * l1[];

    float k2 = (float)(l2[] - l2[]) / (l2[] - l2[]);
    float b2 = l2[] - k2 * l2[];

    Point2f p;
    p.x = (b2 - b1) / (k1 - k2);
    p.y = (k1 * b2 - k2 * b1) / (k1 - k2);

    return p;
}

int main(int argc, char * argv[])
{
    double const THETA =  / ;
    int const  LINE_NUM = ;
    string filename = "../images/bankcard_1.jpg";
    Mat srcImage = imread(filename);

#ifdef _DEBUG
    Mat copyImage;
    srcImage.copyTo(copyImage);
#endif  

    // 灰階化
    Mat grayImage;
    cvtColor(srcImage, grayImage, CV_BGR2GRAY);
    // 高斯濾波
    Mat blurImage;
    GaussianBlur(grayImage, blurImage, Size(, ), , );
    // lsd直線檢測
    Ptr<cv::LineSegmentDetector> lsd;
    lsd = createLineSegmentDetector(cv::LSD_REFINE_NONE, , , , );
    vector<Vec4i> lines;
    lsd->detect(blurImage, lines);
    // 過濾噪聲直線
    Vec4i up_edge;
    Vec4i down_edge;
    Vec4i left_edge;
    Vec4i right_edge;
    double up_tmp = ;
    double down_tmp = ;
    double left_tmp = ;
    double right_tmp = ;
    for (int i = ; i < lines.size(); i++)
    {
        Vec4i& line = lines[i];
        double detaX = abs(line[] - line[]);
        double detaY = abs(line[] - line[]);
        double length = pow(detaX, ) + pow(detaY, );
        // 水準方向
        if (detaX > detaY && atan( * detaY / detaX) < THETA) 
        {
            if (std::max(line[], line[]) < blurImage.rows / )
            {
                if (length > up_tmp)
                {
                    up_tmp = length;
                    up_edge = lines[i];
                }
                continue;
            }
            if (std::max(line[], line[]) > blurImage.rows *  / )
            {
                if (length > down_tmp)
                {
                    down_tmp = length;
                    down_edge = lines[i];
                }
                continue;
            }
        }
        // 垂直方向
        if (detaX < detaY && atan( * detaX / detaY) < THETA)
        {
            if (std::max(line[], line[]) < blurImage.cols / )
            {
                if (length > left_tmp)
                {
                    left_tmp = length;
                    left_edge = lines[i];
                }
                continue;
            }
            if (std::max(line[], line[]) > blurImage.cols *  / )
            {
                if (length > right_tmp)
                {
                    right_tmp = length;
                    right_edge = lines[i];
                }
                continue;
            }
        }
    }

#ifdef _DEBUG
    // 顯示直線
    vector<Vec4i> edges;
    edges.emplace_back(up_edge);
    edges.emplace_back(down_edge);
    edges.emplace_back(left_edge);
    edges.emplace_back(right_edge);
    for (size_t i = ; i < edges.size(); ++i)
    {
        cv::Vec4i& l = edges[i];
        cv::line(copyImage, cv::Point(l[], l[]), cv::Point(l[], l[]), cv::Scalar(, , ), );
    }
    imwrite("lines.jpg", copyImage);
#endif  

    // 求出直線的交點
    vector<cv::Point2f> corss_pos();
    corss_pos[] = CrossPoint(up_edge, left_edge);
    corss_pos[] = CrossPoint(up_edge, right_edge);
    corss_pos[] = CrossPoint(down_edge, left_edge);
    corss_pos[] = CrossPoint(down_edge, right_edge);

#ifdef _DEBUG
    // 顯示交點
    for (size_t i = ; i < corss_pos.size(); ++i)
    {
        circle(copyImage, corss_pos[i], , cv::Scalar(, , ));
    }
    imwrite("corss.jpg", copyImage);
#endif 

    // 透視變換 
    const float w = ;
    const float h = ;
    vector<Point2f> corss_pos_trans();
    corss_pos_trans[] = Point2f(, );
    corss_pos_trans[] = Point2f(w, );
    corss_pos_trans[] = Point2f(, h);
    corss_pos_trans[] = Point2f(w, h);

    Mat transform = getPerspectiveTransform(corss_pos, corss_pos_trans);
    Mat dstImage = Mat::zeros(Size(w, h), srcImage.type());
    cv::warpPerspective(srcImage, dstImage, transform, dstImage.size(), INTER_CUBIC);

#ifdef _DEBUG
    imwrite("dstImage.jpg", dstImage);
#endif 
    //waitKey(0);
    return ;
}
           

Souce Image(圖檔來源于baidu)

切Bank card取number1. 洗菜2. 切菜不是尾巴的尾巴

找到銀行卡的四邊

切Bank card取number1. 洗菜2. 切菜不是尾巴的尾巴

找到銀行卡的四角(紅色的圈不容易看見,仔細看看)

切Bank card取number1. 洗菜2. 切菜不是尾巴的尾巴

透射變換成size = (340, 220)的圖檔

切Bank card取number1. 洗菜2. 切菜不是尾巴的尾巴

2. 切菜

切成LeNet-5能識别的圖像。繼續碼代碼。

#include <iostream>
#include <opencv2/opencv.hpp>

using namespace std;
using namespace cv;


int main(int argc, char * argv[])
{
    string filename = "../images/bankcard.jpg";
    Mat srcImage = imread(filename);

    // 圖檔裁剪
    Mat szImage = srcImage(Rect(, , , ));
    imwrite("szImage.jpg", szImage);

    // 圖像灰階化
    Mat grayImage;
    cvtColor(szImage, grayImage, CV_BGR2GRAY);

    // 濾波
    Mat blurImage;
    GaussianBlur(grayImage, blurImage, Size(, ), , );

    // 形态學梯度
    Mat gradImage;
    Mat morphKernel = getStructuringElement(MORPH_ELLIPSE, Size(, ));
    morphologyEx(blurImage, gradImage, MORPH_GRADIENT, morphKernel);

    // 二值化
    Mat binImage;
    threshold(gradImage, binImage, , , THRESH_BINARY | THRESH_OTSU);

    // 水準膨脹
    Mat dilateImage;
    morphKernel = getStructuringElement(MORPH_RECT, Size(, ));
    morphologyEx(binImage, dilateImage, MORPH_CLOSE, morphKernel);

    // 輪廓
    Mat mask = Mat::zeros(binImage.size(), CV_8UC1);
    vector<vector<Point>> contours;
    vector<Vec4i> hierarchy;
    findContours(dilateImage, contours, hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE, Point(, ));

    // 過濾輪廓
    for (int idx = ; idx >= ; idx = hierarchy[idx][])
    {

        Rect rect = boundingRect(contours[idx]);
        if ((rect.area() > ) && (rect.width > rect.height))
        {
            rectangle(szImage, rect, Scalar(, , ), );
        }
    }

#ifdef _DEBUG
    imwrite("dst.jpg", szImage);
#endif
    waitKey();
    return ;
}
           

Souce Image

切Bank card取number1. 洗菜2. 切菜不是尾巴的尾巴

切割圖檔

切Bank card取number1. 洗菜2. 切菜不是尾巴的尾巴

找出包含銀行卡卡号的區域

切Bank card取number1. 洗菜2. 切菜不是尾巴的尾巴

再利用數字的長、寬就很容易切成單個數字。

不是尾巴的尾巴

暫時就到這裡。銀行卡号的識别要難于身份證、名片以及車牌。為什麼了,因為身份證為白底黑字,大部分名片也是如此,車牌要麼是藍底白字,那麼就是黃底黑字,都很容易得到number。銀行卡正如上圖所示,不是同色也是接近(除非那種黑字老卡),再利用邊緣檢測、門檻值找輪廓的方法很難得到那個數字的圖像。而我所想的方法是标注一些資料(銀行卡數字字型較為單一,是以資料不需要太大),然後嘗試不同的分類(HOG+SVM,ANN,Softmax等),求出最優解。