现在有这么一张图片:
如果说我们要做OCR文字识别,识别卡片中的文字,如果直接检测并识别文字,对于这张图片,也许能够识别,但对于其余像这样的图片,成功率肯定会比较低,如果这张图片是正的,而没有任何偏转,识别率肯定会很高。
不管识别什么,在识别前肯定都要进行图像预处理,接下来就采用OpenCv C++的图像处理方法进行透视变换,将这样一张图片还原为“正”的。
解决思路:
在进行透视变换前,我们需要知道原图目标的四个角点的坐标点,所以,需要进行霍夫直线检测,轮廓查找等操作获取坐标点,之后才可以进行透视变换的操作。
解决方法:
1、图像二值化;
2、形态学操作;
3、轮廓发现与绘制;
4、霍夫直线检测;
5、寻找4条直线两端坐标;
6、方程拟合;
7、求卡片4角点的坐标;
8、透视变换。
1)二值化操作
Mat binary;
threshold(gray_src, binary, 0, 255, THRESH_BINARY | THRESH_OTSU);
imshow("binary Image", binary);
2)形态学操作
这里主要是填充中间的黑色区域,所以用了腐蚀操作。
Mat dst;
Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));
erode(binary, dst, kernel, Point(-1, -1), 1);//1表示迭代1次
//bitwise_not(dst, dst);
imshow("erode Image", dst);
3)轮廓发现与绘制
//---------------轮廓发现----------------
vector> contours;
vector hierarchy;
findContours(dst, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(-1, -1));
//---------------轮廓绘制-----------------
int height = src.rows;
int width = src.cols;
Mat drawImg = Mat::zeros(src.size(), CV_8UC3);
for (size_t i = 0; i < contours.size(); i++)
{
Rect rect = boundingRect(contours[i]); //获取最小外接矩形
if (rect.width > width / 2 && rect.width < width - 5)
{
drawContours(drawImg, contours, static_cast(i),
Scalar(0, 0, 255), 2, 8, hierarchy, 0, Point());
}
}
imshow("drawContours Image", drawImg);
4)霍夫直线检测
//霍夫直线检测
Mat houghImg;
cvtColor(drawImg, houghImg, CV_BGR2GRAY);
vector lines;
//int accu = min(width/2,height/2);
HoughLinesP(houghImg, lines, 1, CV_PI / 180.0, 30, 100, 0);
Mat linesImg = Mat::zeros(src.size(), CV_8UC3);
for (size_t i = 0; i < lines.size(); i++)
{
Vec4i ln = lines[i];
line(linesImg, Point(ln[0], ln[1]), Point(ln[2], ln[3]), Scalar(0, 0, 255), 2, 8, 0);
}
cout << "直线检测的数量:" << lines.size() << endl;
imshow("HoughLines Detect Image", linesImg);
运行结果:
在这之后就可以算出上、下、左、右四条边所在的直线方程,然后两条直线的交点就能求出来。
5)寻找4条直线两端坐标
//寻找与定位上下左右4条线
int theata = 0;
Vec4i topLine, bottomLine;
Vec4i leftLine, rightLine;
for (size_t i = 0; i < lines.size(); i++)
{
Vec4i ln = lines[i];
theata = abs(ln[3] - ln[1]);
if (ln[3] < height / 2 && ln[1] < height / 2 && theata < (height / 2) - 1)
{
topLine = lines[i];
}
if (ln[3] > height / 2 && ln[1] > height / 2 && theata < (height / 2) - 1)
{
bottomLine = lines[i];
}
if (ln[2] < width / 2 && ln[0] < width / 2)
{
leftLine = lines[i];
}
if (ln[2] > width / 2 && ln[0] > width / 2)
{
rightLine = lines[i];
}
}
//打印直线两端坐标
cout << "topLine Point(x1,y1):" << topLine[0] << "," << topLine[1] << " " << "topLine Point(x2,y2):" << topLine[2] << "," << topLine[3] << endl;
cout << "bottomLine Point(x1,y1):" << bottomLine[0] << "," << bottomLine[1] << " " << "bottomLine Point(x2,y2):" << bottomLine[2] << "," << bottomLine[3] << endl;
cout << "leftLine Point(x1,y1):" << leftLine[0] << "," << leftLine[1] << " " << "leftLine Point(x2,y2):" << leftLine[2] << "," << leftLine[3] << endl;
cout << "rightLine Point(x1,y1):" << rightLine[0] << "," << rightLine[1] << " " << "rightLine Point(x2,y2):" << rightLine[2] << "," << rightLine[3] << endl;
//绘制找出来的4条线
line(linesImg, Point(topLine[0], topLine[1]), Point(topLine[2], topLine[3]), Scalar(255, 0, 0), 2, 8, 0);
line(linesImg, Point(bottomLine[0], bottomLine[1]), Point(bottomLine[2], bottomLine[3]), Scalar(255, 0, 0), 2, 8, 0);
line(linesImg, Point(leftLine[0], leftLine[1]), Point(leftLine[2], leftLine[3]), Scalar(255, 0, 0), 2, 8, 0);
line(linesImg, Point(rightLine[0], rightLine[1]), Point(rightLine[2], rightLine[3]), Scalar(255, 0, 0), 2, 8, 0);
6)方程拟合
因为都是直线,所以用y=kx+b就可以求出来。
k=(y1-y2)/(x1-x2);
b=y-kx
而(x1,y1),(x2,y2)之前我们也有求出来。
//上端方程函数
float k1, b1;
k1 = float(topLine[3] - topLine[1]) / float(topLine[2] - topLine[0]);
b1 = float(topLine[1] - k1 * (topLine[0]));
//右端方程函数
float k2, b2;
k2 = float(rightLine[3] - rightLine[1]) / float(rightLine[2] - rightLine[0]);
b2 = float(rightLine[1] - k2 * (rightLine[0]));
//下端方程函数
float k3, b3;
k3 = float(bottomLine[3] - bottomLine[1]) / float(bottomLine[2] - bottomLine[0]);
b3 = float(bottomLine[1] - k3 * (bottomLine[0]));
//左端方程函数
float k4, b4;
k4 = float(leftLine[3] - leftLine[1]) / float(leftLine[2] - leftLine[0]);
b4 = float(leftLine[1] - k4 * (leftLine[0]));
7)求卡片4角点的坐标
两条函数的交点就是坐标点,所以这里求出4条直线两两相交的坐标点。
//左上角交点坐标
Point p1;
p1.x = float((b4 - b1) / (k1 - k4));
p1.y = float(k1 * p1.x + b1);
//右上角交点坐标
Point p2;
p2.x = float((b2 - b1) / (k1 - k2));
p2.y = float(k1 * p2.x + b1);
//右下角交点坐标
Point p3;
p3.x = float((b3 - b2) / (k2 - k3));
p3.y = float(k3 * p3.x + b3);
//左下角交点坐标
Point p4;
p4.x = float((b4 - b3) / (k3 - k4));
p4.y = float(k3 * p4.x + b3);
circle(linesImg, p1, 2, Scalar(0, 255, 0), 2, 8, 0);
circle(linesImg, p2, 2, Scalar(0, 255, 0), 2, 8, 0);
circle(linesImg, p3, 2, Scalar(0, 255, 0), 2, 8, 0);
circle(linesImg, p4, 2, Scalar(0, 255, 0), 2, 8, 0);
cout << "--------------------交点坐标----------------" << endl;
cout << "左上角交点坐标:" << p1.x << "," << p1.y << endl;
cout << "右上角交点坐标:" << p2.x << "," << p2.y << endl;
cout << "右下角交点坐标:" << p3.x << "," << p3.y << endl;
cout << "左下角交点坐标:" << p4.x << "," << p4.y << endl;
imshow("four contours Image", linesImg);
运行结果:
上图中,4个绿色的点就是相交的坐标点,坐标点数值为:
8)透视变换
求出来原图的4个坐标点,现在就可以进行透视变换了。
//透视变换前的四个点所在坐标
vector src_corner(4);
src_corner[0] = p1;
src_corner[1] = p2;
src_corner[2] = p3;
src_corner[3] = p4;
//透视变换后的四个点所在坐标
vector dst_corner(4);
dst_corner[0] = Point(0,0);
dst_corner[1] = Point(src.cols,0);
dst_corner[2] = Point(src.cols,src.rows);
dst_corner[3] = Point(0,src.rows);
Mat resultImg;
Mat M = getPerspectiveTransform(src_corner, dst_corner);
warpPerspective(src, resultImg, M, resultImg.size(),INTER_LINEAR);
imshow("result Image", resultImg);
运行结果:
那么,如果以这样子去进行OCR文字识别的话,成功率应该会有所提升。
附上所有代码:
#include
#include
#include
using namespace cv;
using namespace std;
Mat src, gray_src;
int main(int argc, char** argv)
{
src = imread("D:/test/card.png");
if (src.empty())
{
cout << "图像未找到" << endl;
return -1;
}
imshow("input Image", src);
cvtColor(src, gray_src, CV_BGR2GRAY);
//imshow("gray Image", gray_src);
//------------二值化操作-------------
Mat binary;
threshold(gray_src, binary, 0, 255, THRESH_BINARY | THRESH_OTSU);
imshow("binary Image", binary);
//---------------形态学操作-------------
Mat dst;
Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));
erode(binary, dst, kernel, Point(-1, -1), 1);//1表示迭代1次
//bitwise_not(dst, dst);
imshow("erode Image", dst);
//---------------轮廓发现----------------
vector> contours;
vector hierarchy;
findContours(dst, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(-1, -1));
//---------------轮廓绘制-----------------
int height = src.rows;
int width = src.cols;
Mat drawImg = Mat::zeros(src.size(), CV_8UC3);
for (size_t i = 0; i < contours.size(); i++)
{
Rect rect = boundingRect(contours[i]); //获取最小外接矩形
if (rect.width > width / 2 && rect.width < width - 5)
{
drawContours(drawImg, contours, static_cast(i),
Scalar(0, 0, 255), 2, 8, hierarchy, 0, Point());
}
}
imshow("drawContours Image", drawImg);
//霍夫直线检测
Mat houghImg;
cvtColor(drawImg, houghImg, CV_BGR2GRAY);
vector lines;
//int accu = min(width/2,height/2);
HoughLinesP(houghImg, lines, 1, CV_PI / 180.0, 30, 100, 0);
Mat linesImg = Mat::zeros(src.size(), CV_8UC3);
for (size_t i = 0; i < lines.size(); i++)
{
Vec4i ln = lines[i];
line(linesImg, Point(ln[0], ln[1]), Point(ln[2], ln[3]), Scalar(0, 0, 255), 2, 8, 0);
}
cout << "直线检测的数量:" << lines.size() << endl;
imshow("HoughLines Detect Image", linesImg);
//寻找与定位上下左右4条线
int theata = 0;
Vec4i topLine, bottomLine;
Vec4i leftLine, rightLine;
for (size_t i = 0; i < lines.size(); i++)
{
Vec4i ln = lines[i];
theata = abs(ln[3] - ln[1]);
if (ln[3] < height / 2 && ln[1] < height / 2 && theata < (height / 2) - 1)
{
topLine = lines[i];
}
if (ln[3] > height / 2 && ln[1] > height / 2 && theata < (height / 2) - 1)
{
bottomLine = lines[i];
}
if (ln[2] < width / 2 && ln[0] < width / 2)
{
leftLine = lines[i];
}
if (ln[2] > width / 2 && ln[0] > width / 2)
{
rightLine = lines[i];
}
}
//打印直线两端坐标
cout << "topLine Point(x1,y1):" << topLine[0] << "," << topLine[1] << " " << "topLine Point(x2,y2):" << topLine[2] << "," << topLine[3] << endl;
cout << "bottomLine Point(x1,y1):" << bottomLine[0] << "," << bottomLine[1] << " " << "bottomLine Point(x2,y2):" << bottomLine[2] << "," << bottomLine[3] << endl;
cout << "leftLine Point(x1,y1):" << leftLine[0] << "," << leftLine[1] << " " << "leftLine Point(x2,y2):" << leftLine[2] << "," << leftLine[3] << endl;
cout << "rightLine Point(x1,y1):" << rightLine[0] << "," << rightLine[1] << " " << "rightLine Point(x2,y2):" << rightLine[2] << "," << rightLine[3] << endl;
//绘制找出来的4条线
line(linesImg, Point(topLine[0], topLine[1]), Point(topLine[2], topLine[3]), Scalar(255, 0, 0), 2, 8, 0);
line(linesImg, Point(bottomLine[0], bottomLine[1]), Point(bottomLine[2], bottomLine[3]), Scalar(255, 0, 0), 2, 8, 0);
line(linesImg, Point(leftLine[0], leftLine[1]), Point(leftLine[2], leftLine[3]), Scalar(255, 0, 0), 2, 8, 0);
line(linesImg, Point(rightLine[0], rightLine[1]), Point(rightLine[2], rightLine[3]), Scalar(255, 0, 0), 2, 8, 0);
//imshow("four contours Image", linesImg);
//--------------------拟合方程,求方程-----------------
//y=kx+b
//上端方程函数
float k1, b1;
k1 = float(topLine[3] - topLine[1]) / float(topLine[2] - topLine[0]);
b1 = float(topLine[1] - k1 * (topLine[0]));
//右端方程函数
float k2, b2;
k2 = float(rightLine[3] - rightLine[1]) / float(rightLine[2] - rightLine[0]);
b2 = float(rightLine[1] - k2 * (rightLine[0]));
//下端方程函数
float k3, b3;
k3 = float(bottomLine[3] - bottomLine[1]) / float(bottomLine[2] - bottomLine[0]);
b3 = float(bottomLine[1] - k3 * (bottomLine[0]));
//左端方程函数
float k4, b4;
k4 = float(leftLine[3] - leftLine[1]) / float(leftLine[2] - leftLine[0]);
b4 = float(leftLine[1] - k4 * (leftLine[0]));
//-------------求交点坐标-------------
//左上角交点坐标
Point p1;
p1.x = float((b4 - b1) / (k1 - k4));
p1.y = float(k1 * p1.x + b1);
//右上角交点坐标
Point p2;
p2.x = float((b2 - b1) / (k1 - k2));
p2.y = float(k1 * p2.x + b1);
//右下角交点坐标
Point p3;
p3.x = float((b3 - b2) / (k2 - k3));
p3.y = float(k3 * p3.x + b3);
//左下角交点坐标
Point p4;
p4.x = float((b4 - b3) / (k3 - k4));
p4.y = float(k3 * p4.x + b3);
circle(linesImg, p1, 2, Scalar(0, 255, 0), 2, 8, 0);
circle(linesImg, p2, 2, Scalar(0, 255, 0), 2, 8, 0);
circle(linesImg, p3, 2, Scalar(0, 255, 0), 2, 8, 0);
circle(linesImg, p4, 2, Scalar(0, 255, 0), 2, 8, 0);
cout << "--------------------交点坐标----------------" << endl;
cout << "左上角交点坐标:" << p1.x << "," << p1.y << endl;
cout << "右上角交点坐标:" << p2.x << "," << p2.y << endl;
cout << "右下角交点坐标:" << p3.x << "," << p3.y << endl;
cout << "左下角交点坐标:" << p4.x << "," << p4.y << endl;
imshow("four contours Image", linesImg);
//----------------透视变换---------------------
//透视变换前的四个点所在坐标
vector src_corner(4);
src_corner[0] = p1;
src_corner[1] = p2;
src_corner[2] = p3;
src_corner[3] = p4;
//透视变换后的四个点所在坐标
vector dst_corner(4);
dst_corner[0] = Point(0,0);
dst_corner[1] = Point(src.cols,0);
dst_corner[2] = Point(src.cols,src.rows);
dst_corner[3] = Point(0,src.rows);
Mat resultImg;
Mat M = getPerspectiveTransform(src_corner, dst_corner);
warpPerspective(src, resultImg, M, resultImg.size(),INTER_LINEAR);
imshow("result Image", resultImg);
waitKey(0);
return 0;
}