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