因为二维码本身含有信息,因此可以作为产品的信息载体,如:产品特征。在工业领域常用在产品入库、分拣和包装上。但常常会因为二维码图像污点、光照不均匀以及二维码图像倾斜等原因,使得二维码的识别正确率低,针对这些问题,通过学习贾老师OpenCV课程以及其他作者的经验,实现了基于OpenCV的二维码定位与识别,但仍有一些问题需要进一步改进,如:背景复杂的情况下,应该采用“1 : 1:3 : 1:1”的特点,进一步判断三个定位角的位置。
1、步骤
2、代码实现
使用findContours函数中的hierarchy参数获取“回”形定位角点,然后基于左上角位于直角的特点定位到该点,因为相机相对于二维码平面可能不是垂直关系,因此使用透视变换,而不是仿射变换。
Mat imageContours = Mat::ones(img.size(), CV_8UC1); //最小外接矩形画布
vector<vector<Point>>contours, conts;
vector<Vec4i>hierarchy;
findContours(img, contours, hierarchy, RETR_TREE, CHAIN_APPROX_NONE, Point());
int flag = 0, c = 0;
for (int i = 0; i < contours.size(); i++)
{
if (hierarchy[i][2] != -1 && flag == 0)
{
flag++;
c = i;
}
else if (hierarchy[i][2] == -1)
{
flag = 0;
}
else if (hierarchy[i][2] != -1)
{
flag++;
}
if (flag >= 2)
{
flag = 0;
conts.push_back(contours[c]);
}
}
int count = conts.size();
cout << count << endl;
vector<Point> pointthree;
for (int i = 0; i < count; i++) {
RotatedRect rect = minAreaRect(conts[i]);
Point2f P[4];
rect.points(P);
// circle(imageContours, P[1], 6, (255), 1, 8);
for (int j = 0; j <= 3; j++)
{
line(imageContours, P[j], P[(j + 1) % 4], Scalar(255), 2);
}
imshow("MinAreaRect", imageContours);
pointthree.push_back(rect.center);
}
//找到角度最大的点
double ca[2];
double cb[2];
ca[0] = pointthree[1].x - pointthree[0].x;
ca[1] = pointthree[1].y - pointthree[0].y;
cb[0] = pointthree[2].x - pointthree[0].x;
cb[1] = pointthree[2].y - pointthree[0].y;
double angle1 = 180 / 3.1415*acos((ca[0] * cb[0] + ca[1] * cb[1]) / (sqrt(ca[0] * ca[0] + ca[1] * ca[1])*sqrt(cb[0] * cb[0] + cb[1] * cb[1])));
double ccw1;
if (ca[0] * cb[1] - ca[1] * cb[0] > 0) ccw1 = 0;
else ccw1 = 1;
ca[0] = pointthree[0].x - pointthree[1].x;
ca[1] = pointthree[0].y - pointthree[1].y;
cb[0] = pointthree[2].x - pointthree[1].x;
cb[1] = pointthree[2].y - pointthree[1].y;
double angle2 = 180 / 3.1415*acos((ca[0] * cb[0] + ca[1] * cb[1]) / (sqrt(ca[0] * ca[0] + ca[1] * ca[1])*sqrt(cb[0] * cb[0] + cb[1] * cb[1])));
cout << sqrt(ca[0] * ca[0] + ca[1] * ca[1]) << endl;
cout << sqrt(cb[0] * cb[0] + cb[1] * cb[1]) << endl;
double ccw2;
if (ca[0] * cb[1] - ca[1] * cb[0] > 0) ccw2 = 0;
else ccw2 = 1;
ca[0] = pointthree[1].x - pointthree[2].x;
ca[1] = pointthree[1].y - pointthree[2].y;
cb[0] = pointthree[0].x - pointthree[2].x;
cb[1] = pointthree[0].y - pointthree[2].y;
double angle3 = 180 / 3.1415*acos((ca[0] * cb[0] + ca[1] * cb[1]) / (sqrt(ca[0] * ca[0] + ca[1] * ca[1])*sqrt(cb[0] * cb[0] + cb[1] * cb[1])));
double ccw3;
if (ca[0] * cb[1] - ca[1] * cb[0] > 0) ccw3 = 0;
else ccw3 = 1;
vector<Point2f> poly(4);
if (angle3>angle2 && angle3>angle1)
{
if (ccw3)
{
poly[1] = pointthree[1];
poly[3] = pointthree[0];
}
else
{
poly[1] = pointthree[0];
poly[3] = pointthree[1];
}
poly[0] = pointthree[2];
Point temp(pointthree[0].x + pointthree[1].x - pointthree[2].x, pointthree[0].y + pointthree[1].y - pointthree[2].y);
poly[2] = temp;
// circle(img, poly[0], 6, Scalar(255, 255, 255), 1, 8);
}
else if (angle2>angle1 && angle2>angle3)
{
if (ccw2)
{
poly[1] = pointthree[0];
poly[3] = pointthree[2];
}
else
{
poly[1] = pointthree[2];
poly[3] = pointthree[0];
}
poly[0] = pointthree[1];
Point temp(pointthree[0].x + pointthree[2].x - pointthree[1].x, pointthree[0].y + pointthree[2].y - pointthree[1].y);
poly[2] = temp;
// circle(img, poly[0], 6, Scalar(255, 255, 255), 1, 8);
}
else if (angle1>angle2 && angle1 > angle3)
{
if (ccw1)
{
poly[1] = pointthree[1];
poly[3] = pointthree[2];
}
else
{
poly[1] = pointthree[2];
poly[3] = pointthree[1];
}
poly[0] = pointthree[0];
Point temp(pointthree[1].x + pointthree[2].x - pointthree[0].x, pointthree[1].y + pointthree[2].y - pointthree[0].y);
poly[2] = temp;
// circle(img, poly[0], 6, Scalar(255, 255, 255), 1, 8);
}
vector<Point2f> trans(4);
int temp = 60;
trans[0] = Point2f(0 + temp, 0 + temp);
trans[1] = Point2f(0 + temp, 230 + temp);
trans[2] = Point2f(230 + temp, 230 + temp);
trans[3] = Point2f(230 + temp, 0 + temp);
//获取透视投影变换矩阵
Mat m = getPerspectiveTransform(poly, trans);
//计算变换结果
Mat result;
warpPerspective(img,result,m,Size(350, 350),INTER_LINEAR);
rectangle(result, Rect(10, 10, 330, 330), Scalar(0, 0, 0), 1, 8);
获取到规则二维码图像之后,调用Zbar工具实现二维码的识别。
clock_t start = clock(); // 记录程序开始时间,用于计算扫描二维码耗时
zbar::ImageScanner scanner;
scanner.set_config(zbar::ZBAR_NONE, zbar::ZBAR_CFG_ENABLE, 1);
int width = result.cols;
int height = result.rows;
Image image(width, height, "Y800", result.data, width * height); // 图片格式转换
scanner.scan(image);
Image::SymbolIterator symbol = image.symbol_begin();
if (image.symbol_begin() == image.symbol_end())
{
cout << "查询条码失败,请检查图片!" << endl;
}
for (; symbol != image.symbol_end(); ++symbol)
{
cout << "类型:" << endl << symbol->get_type_name() << endl << endl;
cout << "条码:" << endl << symbol->get_data() << endl << endl;
}
image.set_data(nullptr, 0);
clock_t finish = clock(); // 记录程序结束时间
double time_length = (double)(finish - start) / CLOCKS_PER_SEC; //根据两个时刻的差,计算出扫描的时间
cout << "扫描耗时 " << time_length << " seconds." << endl;
3、结果
透视变换的图像并没有获取到方正的图像,应该是变换角点坐标选取和长宽值不适合的原因,后面再改进。Zbar对于英文、数字等识别效果很好,但是识别中文会出现乱码现象,查阅文章发现是编码和解码格式问题,下次解决。