怪異的标題!!!這道菜不難做,首先列舉一下本次用到的食材:
- 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)
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsICMycTOyETN3EzNxYDM2EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
找到銀行卡的四邊
找到銀行卡的四角(紅色的圈不容易看見,仔細看看)
透射變換成size = (340, 220)的圖檔
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
切割圖檔
找出包含銀行卡卡号的區域
再利用數字的長、寬就很容易切成單個數字。
不是尾巴的尾巴
暫時就到這裡。銀行卡号的識别要難于身份證、名片以及車牌。為什麼了,因為身份證為白底黑字,大部分名片也是如此,車牌要麼是藍底白字,那麼就是黃底黑字,都很容易得到number。銀行卡正如上圖所示,不是同色也是接近(除非那種黑字老卡),再利用邊緣檢測、門檻值找輪廓的方法很難得到那個數字的圖像。而我所想的方法是标注一些資料(銀行卡數字字型較為單一,是以資料不需要太大),然後嘗試不同的分類(HOG+SVM,ANN,Softmax等),求出最優解。