Opencv识别条形码、二维码
最近的一次作业,恰好之前的项目也和c++图像处理相关,那今天就趁着熟练迅速把它搞定。
c++其实关于图像处理的第三方库也有很多,只是大多数库需要根据自己的平台去自己cmake编译,而不是像python那样简单的pip就行。不过多编译几次熟练以后其实也很简单,而且编译的时候还可以摸鱼不香吗(特别是选择 with cuda编译opencv的时候,静静等着就完事了)
今天对于识别条形码,二维码的内容,目前opencv4官方只有识别二维码的类,虽然contrib里也有人提交了识别条形码的,但是就我的使用体验来说效果一般般。我个人使用更多的就是ZBar,当然ZXing也有一定使用,有兴趣的可以去了解try一下。
1.ZBar环境配置
对于win10,直接下载exe安装使用的时候貌似有点问题,估计是因为编译的时候是32位,而我本机64。所以找找相关资料自己再编译一下,大家可以去看看这个博客,如果后续有需要的话我干脆直接把我的放百度云上分享一下
在VS上配置和opencv基本一致,设置好include和lib,link好基本就行了
2.一维码(条形码)识别
给的图片是倒过来的,所以读取图片后我立马flip了一次,这个时候使用的是flip(src,src,-1)
,也就是上下左右都翻转。
原始图片大小有点大,为了后续好显示不用resize,也可以提高识别速度,所以在识别之前就resize。
基本上主函数如下
int main() {
Mat src = imread("D:/10-2.jpg");
flip(src, src, -1);
resize(src, src, Size(src.cols/2,src.rows/2));
code(src.clone());
waitKey(0);
}
code函数就是我们识别条形码的函数,首先对输入图像灰度化,然后就是ZBar的最基本识别流程,这个流程基本所有的例子都一样,这里也不多说。
void code(Mat src) {
Mat image, imageGray, imageGuussian;
Mat imageSobelX, imageSobelY, imageSobelOut;
cvtColor(src, imageGray, COLOR_BGR2GRAY);
imageGray.copyTo(image);
imshow("Source Image", image);
ImageScanner scanner;
scanner.set_config(ZBAR_NONE, ZBAR_CFG_ENABLE, 1);
int width1 = image.cols;
int height1 = image.rows;
uchar* raw = (uchar*)image.data;
Image imageZbar(width1, height1, "Y800", raw, width1 * height1);
scanner.scan(imageZbar);
Image::SymbolIterator symbol = imageZbar.symbol_begin();
if (imageZbar.symbol_begin() == imageZbar.symbol_end())
{
cout << "查询条码失败,请检查图片!" << endl;
}
for (; symbol != imageZbar.symbol_end(); ++symbol)
{
cout << "类型:" << endl << symbol->get_type_name() << endl << endl;
cout << "条码:" << endl << symbol->get_data() << endl << endl;
}
imageZbar.set_data(NULL, 0);
}
3. 二维码的识别
和上图一样,也是倒过来的图像,我们为了后面处理简单,直接flip就行,这样就不用后续为了角度进行一系列旋转、仿射变换。当然这是在有先验知识的情况下,如果需要泛化那就只能老老实实根据轮廓边缘计算旋转角度进行仿射变换或者直接傅里叶变换,根据频域图片结合hough直线也能得到旋转角度。
那现在的难点就在于在这个图片里提取出二维码了,怎么做呢?
- 灰度化、二值化
- 开运算去除二维码外周围的小噪音点
- 闭运算让二维码里面连通
- 找到最大外轮廓,通过面积阈值确定二维码的矩形框,接着计算最小外接矩形框刚好包围二维码
- 有了最小外接矩形框,也就是ROI区域,直接从原图中得到二维码
//找到二维码所在的矩形区域
void Find_QR_Rect(Mat src, vector<Mat>& ROI_Rect)
{
Mat gray;
cv::cvtColor(src, gray, COLOR_RGB2GRAY);
Mat blur;
GaussianBlur(gray, blur, Size(5,5), 0);
Mat bin;
threshold(blur, bin,150,255, THRESH_BINARY);
imshow("bin1", bin);
Mat kernel = getStructuringElement(MORPH_RECT, Size(7,7));
Mat open;
cv::morphologyEx(bin, open, MORPH_OPEN, kernel);
Mat kernel1 = getStructuringElement(MORPH_RECT, Size(1,1));
Mat close;
morphologyEx(open, close, MORPH_CLOSE, kernel1);
//使用RETR_EXTERNAL找到最外轮廓
vector<vector<Point>>MaxContours;
cv::findContours(close, MaxContours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
for (int i = 0; i < MaxContours.size(); i++)
{
Mat mask = Mat::zeros(src.size(), CV_8UC3);
mask = Scalar::all(255);
double area = contourArea(MaxContours[i]);
//cout << area << endl;
//通过面积阈值找到二维码所在矩形区域
if (area > 6000)
{
//计算最小外接矩形
RotatedRect MaxRect = minAreaRect(MaxContours[i]);
//计算最小外接矩形宽高比
double ratio = MaxRect.size.width / MaxRect.size.height;
//cout << ratio << endl;
if (ratio >1)
{
Rect MaxBox = MaxRect.boundingRect();
//将矩形区域从原图抠出来
Mat ROI = src(Rect(MaxBox.tl(), MaxBox.br()));
ROI.copyTo(mask(MaxBox));
ROI_Rect.push_back(mask);
}
}
}
}
二值化图片
二值化的时候对于阈值的选取还是有很大要求的,这里我调了好几次。面积和宽高比的阈值也是需要自己尝试调的
有了ROI之后,接下来就需要确定是不是二维码了。如果是直接用上面的识别流程,如果不是就跳过。
判断是不是二维码主要是依靠它的三个角的特征,也就是三个小方块,我们必须识别到上面检测出的ROI内有三个小矩形框才代表这是一个二维码
在ROI内再次进行矩形检测
- 灰度化、二值化
- 找其中的边缘轮廓
- 判断这个里面存在内嵌轮廓,并且有三个
- 得到最小外接矩形ROI确定这就是一个二维码
//对找到的矩形区域进行识别是否为二维码
int Dectect_QR_Rect(Mat src, Mat& canvas, vector<Mat>& ROI_Rect)
{
vector<vector<Point>>QR_Rect;
Point2f center;
RotatedRect rect;
for (int i = 0; i < ROI_Rect.size(); i++)
{
Mat gray;
cvtColor(ROI_Rect[i], gray, COLOR_BGR2GRAY);
Mat bin;
threshold(gray, bin, 200, 255, THRESH_BINARY_INV|THRESH_OTSU);
vector<vector<Point>>contours;
vector<Vec4i>hierarchy;
findContours(bin, contours, hierarchy, RETR_TREE, CHAIN_APPROX_NONE);
int ParentIndex = -1;
int cn = 0,max_cn=0;
vector<Point>rect_points;
for (int i = 0; i < contours.size(); i++)
{
if (hierarchy[i][2] != -1 && cn == 0)
{
ParentIndex = i;
cn++;
}
else if (hierarchy[i][2] != -1 && cn == 1)
{
cn++;
}
else if (hierarchy[i][2] == -1)
{
ParentIndex = -1;
cn = 0;
}
if (hierarchy[i][2] != -1 && cn == 2)
{
drawContours(canvas, contours, ParentIndex, Scalar::all(255), -1);
imshow("canvas", canvas);
rect = minAreaRect(contours[ParentIndex]);
center = rect.center;
rect_points.push_back(rect.center);
}
max_cn = max(max_cn, cn);
}
for (int i = 0; i < rect_points.size(); i++)
{
line(canvas, rect_points[i], rect_points[(i + 1) % rect_points.size()], Scalar::all(255), 5);
}
//检测到三个方块
if(max_cn==2)QR_Rect.push_back(rect_points);
}
return QR_Rect.size();
}
最后主函数里先检测ROI,然后判断这个ROI是不是二维码,如果是根据ROI找到二维码区域进行识别
int main()
{
Mat src = imread("D:/10-1.jpg");
flip(src, src, -1);
resize(src, src, Size(src.cols / 4,src.rows/ 4));
vector<Mat>ROI_Rect;
Find_QR_Rect(src.clone(), ROI_Rect);
Mat canvas = Mat::zeros(src.size(), src.type());
int flag = Dectect_QR_Rect(src.clone(), canvas, ROI_Rect);
if (flag <= 0)
{
std::cout << "没有检测到二维码!" << endl;
system("pause");
return 0;
}
std::cout << "检测到" << flag << "个二维码。" << endl;
if (flag) {
for (int i = 0; i < ROI_Rect.size(); i++)
{
Mat gray;
cvtColor(ROI_Rect[i], gray, COLOR_BGR2GRAY);
Mat bin;
threshold(gray, bin, 200, 255, THRESH_BINARY_INV | THRESH_OTSU);
imshow("bin", bin);
code(bin);
//code2(bin);
}
}
//框出二维码所在位置
Mat gray;
cvtColor(canvas, gray, COLOR_BGR2GRAY);
vector<vector<Point>>contours;
findContours(gray, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
Point2f points[4];
for (int i = 0; i < contours.size(); i++)
{
RotatedRect rect = minAreaRect(contours[i]);
Point2f center = rect.center;
Mat rot_mat = getRotationMatrix2D(center, rect.angle, 1.0);//求旋转矩阵
Mat result1 = src(Rect(center.x - (rect.size.width / 2), center.y - (rect.size.height / 2), rect.size.width, rect.size.height));//提取ROI
rect.points(points);
for (int j = 0; j < 4; j++)
{
line(src, points[j], points[(j + 1) % 4], Scalar(0, 255, 0), 2);
}
}
imshow("source", src);
waitKey(0);
destroyAllWindows();
return 0;
}
到这里,识别二维码就实现了。
4. Opencv识别二维码
void code2(Mat src) {
QRCodeDetector qrDecoder = QRCodeDetector::QRCodeDetector();
Mat bbox, rectifiedImage;
string data = qrDecoder.detectAndDecode(src, bbox, rectifiedImage);
if (data.length() > 0) {
cout << "识别到的二维码信息:" << data << endl;
rectifiedImage.convertTo(rectifiedImage, CV_8UC3);
imshow("矫正后二维码", rectifiedImage);
}
else {
cout << "未识别出二维码信息!" << endl;
}
}
这个是使用opencv识别二维码的流程,但是这次opencv并没有识别出来,所以没有用这个。
结束
老师上课用的是Halcon,但是推荐我们自己用c++实现一些基本的图像处理算法,所以我就用opencv结合ZBar来做这次作业。二维码识别的难点更多在于如果合理的选取阈值才能完美的找到二维码那个矩形框以及角度变换问题(我这里偷懒了没有实现直接flip),但是就算有了二维码也不一定能用ZBar或者QRCodeDetector很好的识别出来结果,这个就很伤脑筋。