OpenCV二维码检测流程全解析

引入必要的OpenCV和C++库

首先,程序包含了OpenCV库和C++标准库的头文件,这些库提供了处理图像所需的各种功能。

#include <opencv2/core.hpp> // OpenCV核心功能#include <opencv2/imgcodecs.hpp> // 图像编解码#include <opencv2/highgui.hpp> // GUI#include <opencv2/imgproc.hpp> // 图像处理#include <iostream> // 输入输出流#include <set> // 集合#include <opencv2/opencv.hpp> // OpenCV主要功能#include <opencv2/imgproc/types_c.h> // 旧版的图像处理

使用namespace简化代码中频繁使用的命名空间引用。

using namespace cv;

using namespace std;

定义结构体和全局变量

定义一个结构体index_用于存储相关联的轮廓索引,以及一个全局变量vin存储这些结构体的向量,用于后续处理。

RNG rng(12345);

struct index_ {

int a1; int a2; int a3;};

index_ in;

vector<index_> vin;

主函数和图像预处理

加载图像,调整大小至500x500像素,以便于处理。对图像进行对比度和亮度调节,目的是增强图像中的二维码部分,使其在后续步骤中更容易被识别。

String srcImagePath("D:\\Users\\Desktop\\opencv二维码定位图案检测(c++)\\8.jpg");Mat srcImage = imread(srcImagePath, IMREAD_COLOR);resize(srcImage, srcImage, Size(500, 500));

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); } }}

灰度转换、滤波、二值化

将调整后的图像转换为灰度图像,并应用双边滤波器进行平滑处理。然后对图像进行二值化处理,这是为了准备图像的边缘检测步骤。

cvtColor(contrastImage, grayImage, COLOR_BGR2GRAY);bilateralFilter(grayImage, filterImage, 13, 26, 6);threshold(filterImage, binaryImage, 210, 255, THRESH_BINARY_INV);

边缘检测、腐蚀和膨胀处理

使用Canny算法进行边缘检测,以便于识别图像中的直线。对二值化后的图像进行腐蚀和膨胀处理,旨在去除噪点并强化主要特征,特别是二维码的边缘。

Canny(dilateImage, cannyImage, 10, 100, 3, false);

直线检测

通过霍夫变换在Canny边缘检测得到的图像上检测直线。霍夫变换是一种在图像中识别几何形状(如直线和圆)的技术。

vector<Vec2f> lines;HoughLines(cannyImage, lines, 5, CV_PI / 180, 100);

这段代码执行霍夫直线检测,lines存储检测到的直线,每条直线由两个参数表示:距离ρ(rho)和角度θ(theta)。参数设定确保能有效检测到直线。

直线过滤与删除相似直线

为了减少处理的直线数量,提高效率和准确性,程序设计了一段逻辑来删除相似的直线,只保留最关键的直线。

double A = 50.0;double B = CV_PI / 180 * 20;set<size_t> removeIndex;while (1) { for (size_t i = 0; i < resLines.size(); i++) { for (size_t j = i + 1; j < resLines.size(); j++) { // 直线参数 float rho1 = resLinesi0, theta1 = resLinesi1; float rho2 = resLinesj0, theta2 = resLinesj1;

// 角度调整,确保比较的一致性 if (theta1 > CV_PI) theta1 -= CV_PI; if (theta2 > CV_PI) theta2 -= CV_PI;

// 判断是否为相似直线并标记删除 bool thetaFlag = abs(theta1 - theta2) <= B || (theta1 > CV_PI / 2 && theta2 < CV_PI / 2 && CV_PI - theta1 + theta2 < B) || (theta2 > CV_PI / 2 && theta1 < CV_PI / 2 && CV_PI - theta2 + theta1 < B); if (abs(rho1 - rho2) <= A && thetaFlag) { removeIndex.insert(j); } } }

// 删除标记的直线 vector<Vec2f> newLines; for (size_t i = 0; i < resLines.size(); i++) { if (removeIndex.find(i) == removeIndex.end()) { newLines.push_back(resLinesi); } } resLines = newLines;

// 直线数量达到目标值则终止循环 if (resLines.size() == 4) break;}

该段代码通过比较直线的ρ和θ值,删除相似的直线,直至剩下4条代表二维码边缘的直线。

计算直线交点

接下来,计算剩余直线的交点,这些交点预计位于二维码的四个角落。

double threshold = 0.2 * min(srcImage.rows, srcImage.cols);vector<Point> points;for (int i = 0; i < fourLines.size(); i++) { for (int j = i + 1; j < fourLines.size(); j++) { // 提取直线参数 double rho1 = fourLinesi0, theta1 = fourLinesi1; double rho2 = fourLinesj0, theta2 = fourLinesj1;

// 调整θ值以处理斜率无穷大的情况 if (theta1 == 0) theta1 = 0.01; if (theta2 == 0) theta2 = 0.01;

// 计算直线交点 double a1 = cos(theta1), a2 = cos(theta2); double b1 = sin(theta1), b2 = sin(theta2);double x = (rho2 * b1 - rho1 * b2) / (a2 * b1 - a1 * b2); double y = (rho1 - a1 * x) / b1;

// 保证交点在图像范围内 Point pt(cvRound(x), cvRound(y)); if (pt.x <= srcImage.cols + threshold && pt.x >= -threshold && pt.y < srcImage.rows + threshold && pt.y >= -threshold) { points.push_back(pt); }}

这段代码利用数学方法来计算直线的交点。对于直线方程的每一对组合,通过解析几何的方式计算它们的交点,然后校验这个交点是否位于图像的有效范围内。这样得到的点(如果在有效范围内)被认为是二维码的角点之一。

二维码区域的仿射变换

获取到四个角点后,接下来的步骤是将这部分图像进行仿射变换,以校正图像,使得二维码摆正方便后续解码。

Point2f srcTri4;Point2f dstTri4;// 假设srcTri已经根据points数组正确赋值// dstTri是变换后希望得到的正方形坐标

Mat transmtx = getPerspectiveTransform(srcTri, dstTri);warpPerspective(srcImage, perspImage, transmtx, perspImage.size());

通过getPerspectiveTransform和warpPerspective函数,计算从原图到目标图像的透视变换矩阵,并应用这个变换。这样,即便原始图像中的二维码是倾斜的,也能被矫正为正面朝上的状态,便于解码。

寻找定位角和解码

在变换后的图像中,需要再次寻找二维码的定位角。这一过程涉及到对图像轮廓的检测和分析,从而找到标志二维码位置的特定图形(通常是三个较大的正方形)。

findContours(perspImage, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE);// 分析轮廓和层级关系,寻找定位角

对于每一个找到的轮廓,通过几何关系和层级关系来判定哪些是二维码的定位角。之后,可以根据这些定位角来进一步处理图像,或者直接在矫正后的图像上进行二维码的解码。

string qrCodeText;if (decodeQRCode(perspImage, qrCodeText)) { cout << "Decoded QR Code Text: " << qrCodeText << endl;} else { cout << "QR Code not detected or decoded." << endl;}

这里decodeQRCode函数负责实际的二维码解码工作,它尝试从提供的图像中检测并解码二维码。如果成功,解码的文本内容将被打印出来。

总结

使用OpenCV进行二维码检测与解码的完整过程。从图像的预处理开始,经过边缘检测、直线检测与过滤、计算交点、图像矫正,最终到二维码的解码,每一步都是为了使二维码图像能够被正确识别和解析。

相关推荐
博客180020 小时前
酷宝的使用方法,超好用的免费界面库,C++、MFC可用
c++·mfc·界面库·库来帮·酷宝
郝学胜_神的一滴1 天前
CMake 026:属性体系精讲、四大作用域全解 & 实战代码落地
c++·cmake
众少成多积小致巨2 天前
JNI (Java Native Interface) 技术手册中文参考指南
android·java·c++
兵慌码乱2 天前
基于 MediaPipe 与 PySide2 的手势交互音乐控制系统实现:轻量化视觉交互全流程解析
python·opencv·计算机视觉·人机交互·手势识别·mediapipe·pyside2
clint4566 天前
C++进阶(1)——前景提要
c++
夜悊6 天前
C++代码示例:进制数简单生成工具
c++
郝学胜_神的一滴6 天前
CMake 021: IF 条件判据详诠
c++·cmake
_wyt0016 天前
洛谷 B3930 [GESP202312 五级] 烹饪问题 题解
c++·gesp
玖玥拾7 天前
C/C++ 数据结构(七)栈、容器适配器
c语言·数据结构·c++··容器适配器
梦想三三7 天前
OpenCV银行卡数字识别项目(图像预处理与字符分割)
人工智能·opencv·计算机视觉