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 = resLines[i][0], theta1 = resLines[i][1]; float rho2 = resLines[j][0], theta2 = resLines[j][1];

// 角度调整,确保比较的一致性 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(resLines[i]); } } 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 = fourLines[i][0], theta1 = fourLines[i][1]; double rho2 = fourLines[j][0], theta2 = fourLines[j][1];

// 调整θ值以处理斜率无穷大的情况 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 srcTri[4];Point2f dstTri[4];// 假设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进行二维码检测与解码的完整过程。从图像的预处理开始,经过边缘检测、直线检测与过滤、计算交点、图像矫正,最终到二维码的解码,每一步都是为了使二维码图像能够被正确识别和解析。

相关推荐
CV工程师小林44 分钟前
【C++】C++11
开发语言·c++
L_qingting1 小时前
C++ 设计模式之享元模式
c++·设计模式·享元模式
DieSnowK2 小时前
[C++][设计模式][备忘录模式]详细讲解
开发语言·c++·设计模式·重构·面向对象·备忘录模式·新手向
小毕超2 小时前
OpenCV 调用自定义训练的 YOLO-V8 Onnx 模型
人工智能·opencv·yolo
✿ ༺ ོIT技术༻2 小时前
BFS:队列+树的宽搜
c++·算法·宽度优先
森龙安2 小时前
OpenGL3.3_C++_Windows(24)
c++·图形渲染
L_qingting3 小时前
C++ 设计模式之模板方法模式
c++·设计模式·模板方法模式
L_qingting3 小时前
C++ 设计模式之解释器模式
c++·设计模式·解释器模式
Dongliner~3 小时前
【C++:list】
开发语言·c++
情系明明3 小时前
赋值运算符函数 c++
开发语言·c++