基于OpenCV实现二维码等图像的检测与矫正

1. 效果展示

首先先展示一下效果,左边是原图,右边是通过矫正后的图片。该算法适用于黑白较为分明的图像,但对于一些极端情况(比如大面积阴影,污点等等),效果不佳,因此有一定局限性。这里也仅仅提供一个算法思路,供人借鉴。
在这里插入图片描述

2. 算法流程

算法主要流程主要分为:

1)对比度亮度调整
通过对比度亮度调整,增加二维码和背景的分离度。
2)滤波降噪
通过滤波降噪,去除部分图像噪点。
3)反二值化
反二值化,进一步强化边界,增强分离度;同时为腐蚀膨胀做铺垫。
4)腐蚀膨胀处理
腐蚀操作处理图像上小的污点;膨胀操作勾画二维码大致轮廓区域(近似四边形)。
5)Canny边缘检测
Canny边缘检测可以对膨胀处理生成的轮廓区域进行边缘勾画。
6)Hough算子拟合直线
利用Hough算子可以对轮廓边缘线进行近似拟合,生成多个拟合直线。
7)计算二维码四个顶点坐标
利用算法从众多直线中,找到四根边界线,并计算出四个交点(即轮廓四边形顶点)
8)利用顶点坐标进行仿射变换
对四个顶点进行顺时针排序,并按照顶点顺序进行仿射变换。

在这里插入图片描述

3. 算法分析(带示例)

1)对比度亮度调整

首先将图片resize到500x500,减小后续图片处理的计算量。随后可以通过对比度因子和亮度因子,对对比度及亮度进行调整。

    String srcImagePath( "./result/test.jpg" ); // 原图路径
    Mat srcImage;
	srcImage = imread( srcImagePath, IMREAD_COLOR ); // 载入初始图片
	resize(srcImage, srcImage, Size(500, 500));   //图片缩放为500*500进行后续计算
	imwrite("./result/Src_Image.jpg", srcImage);   //保存图片

	Mat contrastImage = Mat::zeros( srcImage.size(), srcImage.type() );    //亮度与对比度调节
	double alpha = 1.8;  //对比度因子
    int beta = -30;   //亮度因子
    for( int y = 0; y < srcImage.rows; y++ ) {
   
        for( int x = 0; x < srcImage.cols; x++ ) {
   
            for( int c = 0; c < 3; c++ ) {
   
                contrastImage.at<Vec3b>(y,x)[c] =
					saturate_cast<uchar>( alpha*( srcImage.at<Vec3b>(y,x)[c] ) + beta );
            }
        }
    }
	imwrite("./result/Contrast_Image.jpg", contrastImage);

调整结果如下:
对比度亮度调整结果

2)滤波降噪

将对比度调整后的图片进行灰度转化,降低通道数。随后对该灰度图进行滤波,主要作用是为了降低噪声。不同的噪声类型,可以采用不用的滤波方法,包括高斯滤波、中值滤波、甚至自定义滤波方法。这里我采用的是双边滤波,保留边缘信息。

	Mat grayImage;
	cvtColor( contrastImage, grayImage, COLOR_BGR2GRAY );  //转化为灰度图
	imwrite("./result/Gray_Image.jpg", grayImage);  //保存灰度图

	Mat filterImage;
	bilateralFilter( grayImage, filterImage, 13, 26, 6 );  //双边滤波
	//medianBlur ( grayImage, filterImage, 3 );          //中值滤波
	imwrite("./result/Filter_Image.jpg", filterImage);  //保存滤波后图片

滤波后结果如下:
在这里插入图片描述

3)反二值化

二值化这一步,将二维码部分与背景进一步分离。同时对结果进行反色,即二维码黑色部分变成白色,背景变成黑色,以便于对后续图像进行腐蚀膨胀等操作。

	Mat binaryImage;
	threshold( filterImage, binaryImage, 210, 255, THRESH_BINARY_INV ); //反二值化
	imwrite("./result/Binary_Image.jpg", binaryImage);  //保存二值化图片

反二值化结果如下:
在这里插入图片描述

4)腐蚀膨胀处理

首先通过一到两次的腐蚀处理,原本图像上非二维码区域的小污点将被清理掉;随后进行多次膨胀操作,勾画二维码所在位置的大致区域,表现为一个近似的四边形。

	Mat erodeImage;
	erode( binaryImage, erodeImage, Mat(), Point(-1, -1), 2 );  //腐蚀化
	imwrite("./result/Erode_Image.jpg", erodeImage);

	Mat dilateImage;
	dilate( erodeImage, dilateImage, Mat(), Point(-1, -1), 19 ); //膨胀化
	imwrite("./result/Dilate_Image.jpg", dilateImage);

腐蚀膨胀操作结果如下:
在这里插入图片描述

5)Canny边缘检测

利用Canny算法对上述结果进行边缘检测,勾勒出近似四边形的边界。

	Mat cannyImage;
	Canny( dilateImage, cannyImage, 10, 100, 3, false); //canny边缘检测
	imwrite("./result/Canny_Image.jpg", cannyImage);

边缘检测结果如下:
在这里插入图片描述

6)Hough算子拟合直线

利用Hough算子对上述结果图进行直线拟合,找到近似四边形的所有近似边的直线,并画出所有直线。(可以通过修改参数以提高或者降低拟合程度,保证每条边至少能拟合一条直线)

	//********在canny图上画出所有hough拟合直线
	Mat allLinesImage = cannyImage.clone();
    vector<Vec2f> lines;
    HoughLines( allLinesImage, lines, 5, CV_PI/180, 100 );   //hough算子拟合直线
    for( size_t i = 0; i < lines.size(); i++ )
    {
   
        float rho = lines[i][0];
        float theta = lines[i][1];
        double a = cos(theta), b = sin(theta);
        double x0 = a*rho, y0 = b*rho;
        Point pt1(cvRound(x0 + 1000*(-b)),
                  cvRound(y0 + 1000*(a)));
        Point pt2(cvRound(x0 - 1000*(-b)),
                  cvRound(y0 - 1000*(a)));
        line( allLinesImage, pt1, pt2, Scalar(255), 1, 8 );
    }
	imwrite("./result/AllLines_Image.jpg", allLinesImage);

Hough拟合直线结果如下:
在这里插入图片描述

7)计算二维码四个顶点坐标

这里分为两个步骤:

1. 从所有直线中,删除相似的直线,保留差距较大的直线。依此得到符合要求的四条边。

其算法思路就是:对所有直线进行两两比较,如果某两条直线角度(theta)之差以及距离原点距离(rho)之差都分别小于某一阈值,则认为这两条直线相似,需要删去其中一条直线。比较完毕后,若发现剩余直线数量大于4,则提高阈值(相反,小于4,则适当降低阈值),继续进行新一轮的比较算法,直到剩余直线数量为4。

	//********删除相似直线直到只剩4条拟合直线
	double A = 50.0;  //初始距离阈值:50
    double B = CV_PI / 180 * 20; //初始角度阈值:20度
    
	vector<Vec2f> resLines (lines);
	set<size_t> removeIndex;    //记录需要删除的直线编号
	int countLess4 = 0;  //死循环检测
	int countMore4 = 0;  //死循环检测
	while(1){
   
		for( size_t i = 0; i < resLines.size(); i++ ){
   
			for( size_t j = i+1; j < resLines.size(); j++ ){
   
				float rho1 = resLines
### 使用Python库技术纠正畸变的QR码 对于处理纠正畸变的QR码,可以采用OpenCV以及Pyzbar等库来实现图像预处理解码功能。通过这些工具能够有效地识别并校正由于拍摄角度或其他因素造成的变形。 #### 图像预处理阶段 为了改善QR码读取的成功率,在尝试解析之前通常会先执行一些基本的操作,比如灰度化、二值化、去噪平滑滤波等操作以增强特征对比度[^1]: ```python import cv2 import numpy as np def preprocess_image(image_path): img = cv2.imread(image_path) # Convert to grayscale gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # Apply GaussianBlur to reduce noise and improve edge detection blurred_img = cv2.GaussianBlur(gray_img, (5, 5), 0) # Use adaptive thresholding instead of simple binary thresholding. thresh_img = cv2.adaptiveThreshold(blurred_img, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, blockSize=19, C=2) return thresh_img ``` #### 定位并提取QR区域 利用轮廓检测技术找到可能存在的矩形边界,并进一步筛选出最有可能代表QR码的部分。这一步骤有助于去除背景干扰并将关注点集中在目标对象上: ```python def find_qr_contours(preprocessed_img): contours, _ = cv2.findContours(preprocessed_img.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) qr_contour = None for contour in contours: perimeter = cv2.arcLength(contour, True) approx_polygon = cv2.approxPolyDP(contour, 0.04 * perimeter, True) if len(approx_polygon) == 4: # Assuming a quadrilateral shape represents QR code area qr_contour = approx_polygon break return qr_contour ``` #### 应用透视变换恢复原始比例 一旦确定了四个顶点的位置,则可以通过`cv2.getPerspectiveTransform()`函数计算转换矩阵,并应用到整个图片上来获得未经扭曲的标准视图: ```python def apply_perspective_transform(original_img, qr_contour_points): pts_src = np.array(qr_contour_points).reshape(-1, 2) width, height = max(pts_src[:, 0]) - min(pts_src[:, 0]), max(pts_src[:, 1]) - min(pts_src[:, 1]) pts_dst = np.float32([[width, 0], [0, 0], [0, height], [width, height]]) M = cv2.getPerspectiveTransform(np.float32([pts_src]), pts_dst) warped_img = cv2.warpPerspective(original_img, M, (int(width), int(height))) return warped_img ``` #### 解析最终结果 最后借助于专门用于扫描条形码/二维码的数据捕捉模块——pyzbar完成实际的信息解读工作: ```python from pyzbar import pyzbar def decode_qrcode(warped_img): decoded_objects = pyzbar.decode(warped_img) data_list = [] for obj in decoded_objects: data_list.append(obj.data.decode('utf-8')) return data_list ``` 上述方法提供了一套完整的流程来解决因视角变化而引起的QR码失真问题。当然还有其他更高级的技术可用于特定场景下的优化调整,例如基于深度学习的目标检测模型等。
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值