Opencv的使用小教程|Opencv的使用小教程3——利用轮廓检测实现二维码定位


Opencv的使用小教程3——利用轮廓检测实现二维码定位

  • 二维码具有什么特征
  • 实现效果
  • 识别二维码的流程
    • 1、预处理图像
    • 2、寻找轮廓
    • 3、通过寻找到的轮廓确定“回”的位置
    • 4、创建一张新图,并在新图上画出识别到的“回”并连线
    • 5、寻找直角
    • 6、确定另外两个点的次序关系
    • 7、计算旋转角
    • 8、完成二维码的旋转
  • 全部代码

好好学习噢!
Opencv的使用小教程|Opencv的使用小教程3——利用轮廓检测实现二维码定位
文章图片

二维码具有什么特征 二维码就是两个维度的条形码,平常我们在生活中随处可见,“QR”是“Quick Response”的缩写,它指的就是可以对隐藏在二维码中的数据实现快速读取。QR码相对传统条形码的优势是数据存储量大和高容错性。
我们都知道二维码有三个非常明显的黑框,就像下面的图显示的一样。
Opencv的使用小教程|Opencv的使用小教程3——利用轮廓检测实现二维码定位
文章图片

而这三个框框因为其明显的特征,它的作用也是用于二维码的定位。
因此,要实现二维码的定位,最重要的一点就是定位这三个框框,三个回。
这三个回有什么特点呢?
1、每一个“回”在包括最外边的轮廓的情况下,都存在三个轮廓,分别是:内部黑色正方形轮廓,白色正方形轮廓,外部轮廓。
2、每一个“回”的黑白框框的比例大概为1:3:1:1。
Opencv的使用小教程|Opencv的使用小教程3——利用轮廓检测实现二维码定位
文章图片

3、左上角的“回”与其它的顶点的夹角为90度
实现效果 原图(图片来自于网络):
Opencv的使用小教程|Opencv的使用小教程3——利用轮廓检测实现二维码定位
文章图片

识别结果:
Opencv的使用小教程|Opencv的使用小教程3——利用轮廓检测实现二维码定位
文章图片

Opencv的使用小教程|Opencv的使用小教程3——利用轮廓检测实现二维码定位
文章图片

识别二维码的流程 在看流程前,可以前往我的github下载源码https://github.com/bubbliiiing/QRcode-location
1、预处理图像 在正式确定二维码的位置之前,首先要对原始图像进行处理,不能让原始图像很随意的直接使用,这其中包括一些重要的步骤:
1、利用cvtColor转化成灰度图像。
2、利用blur平滑图像,去除一些噪点。
3、利用convertScaleAbs实现图像的对比度增强。便于区分特征。
4、利用equalizeHist计算直方图,直方图的作用也是为了使得整个图像的区分度更大。
5、利用threshold阈值操作,将整个图像分为黑白两个极点,便于之后寻找轮廓。
以上所有的步骤都是为了让图像更便于寻找轮廓。
一个彩色的二维码图像,在经过如上的处理之后,可能会得到如下的图像:
Opencv的使用小教程|Opencv的使用小教程3——利用轮廓检测实现二维码定位
文章图片

2、寻找轮廓 在得到上述图像后,重要的是如何找到三个“回”,根据“回”的特点:每一个“回”在包括最外边的轮廓的情况下,都存在三个轮廓,分别是:内部黑色正方形轮廓,白色正方形轮廓,外部轮廓,我们可以完成回的寻找。
本文利用opencv自带的findContours函数寻找轮廓。
findContours的调用方式如下:
findContours(srcGray, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE);

其中:
1、srcGray是需要寻找轮廓的图像;
2、contours是寻找到的轮廓的存储变量,其变量类型为vector,向量内每个元素保存了一组由连续的Point点构成的点的集合的向量。
3、hierarchy是轮廓之间的关系,其变量类型为vector,每一个hierarchy的元素包括了四个int的数据,分别表示第i个轮廓的后一个轮廓、前一个轮廓、子轮廓、父轮廓的索引编号。
3、通过寻找到的轮廓确定“回”的位置 在这里一部分,又要开始提到“回”的特点,其重要特点为存在三个轮廓,分别是:内部黑色正方形轮廓,白色正方形轮廓,外部轮廓。
如果我们可以找到内部具有两个轮廓的轮廓,大概率就是我们需要的“回”的位置,这里我给标亮了,大家应该看的明白。
Opencv的使用小教程|Opencv的使用小教程3——利用轮廓检测实现二维码定位
文章图片

我们对所有的轮廓进行判断,其内部是否有两个轮廓。
判断的方式如下,我用伪代码的形式呈现:
# ic用于确定这是“回”字判别中的第几个轮廓 for 轮廓i in 所有轮廓: if 该轮廓有子轮廓且ic == 0: 保存该轮廓id ic++ elif 该轮廓是父轮廓: ic++ elif 该轮廓不是父轮廓: 初始化ic 初始化父轮廓id if ic == 2: # 表示总共检测到三个连续的轮廓,满足外轮廓中有两个轮廓的要求 if(IsQrPoint): # IsQrPoint该函数通过面积二次判断是否为“回”字 保存该轮廓 # 此时已经检测到一个“回" 初始化ic 初始化父轮廓id

【Opencv的使用小教程|Opencv的使用小教程3——利用轮廓检测实现二维码定位】该流程执行同样得益于findContours在父轮廓与子轮廓是连续排序的。即父轮廓后一个轮廓是其子轮廓。
具体的执行代码如下:
for (int i = 0; i < contours.size(); i++) { // 判断是否为父轮廓 if (hierarchy[i][2] != -1 && ic == 0) { parentIdx = i; ic++; } // 判断是否是父轮廓内的子轮廓 else if (hierarchy[i][2] != -1) { ic++; } else if (hierarchy[i][2] == -1) { parentIdx = -1; ic = 0; } // 判断是否积累检测到三个轮廓 if (ic == 2) { //通过图像处理进行深层次的判断 if (IsQrPoint(contours[parentIdx], src)) { RotatedRect rect = minAreaRect(Mat(contours[parentIdx])); // 画图部分 Point2f points[4]; rect.points(points); for (int j = 0; j < 4; j++) { line(src, points[j], points[(j + 1) % 4], Scalar(0, 255, 0), 2); } drawContours(canvas, contours, parentIdx, Scalar(0, 0, 255), -1); // 如果满足条件则存入 center_all.push_back(rect.center); numOfRec++; } ic = 0; parentIdx = -1; } }

4、创建一张新图,并在新图上画出识别到的“回”并连线 该部分的目的主要是在新图上绘画出三个“回”,然后利用findContours得到轮廓,在新图上,一共会获得四个轮廓,分别是三个“回”的轮廓和整个QRcode的轮廓,然后为所有的轮廓利用minAreaRect函数构建最小的矩形,之所以这样做是因为原图干扰太多,直接在新图上构建矩形可以很容易避免干扰,只要识别“回”识别的正确,就一定可以得到正确的矩形。
在该部分需要执行的流程是:
1、利用findContours得到轮廓。
2、利用minAreaRect得到与轮廓最相符合的矩形
3、对矩形面积进行检测筛选,最大面积的矩形就是QRcode的矩形,比较小的三个矩形是“回”
具体执行代码如下
vector contours3; Mat canvasGray; cvtColor(canvas, canvasGray, COLOR_BGR2GRAY); findContours(canvasGray, contours3, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE); vector maxContours; double maxArea = 0; // 在原图中画出二维码的区域for (int i = 0; i < contours3.size(); i++) { RotatedRect rect = minAreaRect(contours3[i]); Point2f boxpoint[4]; rect.points(boxpoint); for (int i = 0; i < 4; i++) line(src, boxpoint[i], boxpoint[(i + 1) % 4], Scalar(0, 0, 255), 3); double area = contourArea(contours3[i]); if (area > maxArea) { maxContours = contours3[i]; maxArea = area; } } imshow("src", src); if (numOfRec < 3) { waitKey(10); continue; } RotatedRect rect = minAreaRect(Mat(maxContours));

5、寻找直角 在上几步中,我们已经获得的了“回”的位置和整个二维码的大概轮廓,这一步,我们主要是对二维码的方向进行矫正,对二维码而言,其三个“回”存在次序关系,左上角的“回”与另外两个“回”构成直角,像这样。
Opencv的使用小教程|Opencv的使用小教程3——利用轮廓检测实现二维码定位
文章图片

如何确定三个回哪个是直角呢,我们通过向量垂直公式判断:
两个向量垂直,有垂直定理: 若设向量a=(x1,y1),b=(x2,y2) ,a⊥b的充要条件是a·b=0,即(x1x2+y1y2)=0。
在此处我们只需要将任意两个“回”的中心坐标相减就可以得到一个“回”到另一个回的向量。
通过如下方式判断:
int leftTopPoint(vector centerPoint) { int minIndex = 0; int multiple = 0; int minMultiple = 10000; multiple = (centerPoint[1].x - centerPoint[0].x)*(centerPoint[2].x - centerPoint[0].x) + (centerPoint[1].y - centerPoint[0].y)*(centerPoint[2].y - centerPoint[0].y); if (minMultiple > multiple){ minIndex = 0; minMultiple = multiple; } multiple = (centerPoint[0].x - centerPoint[1].x)*(centerPoint[2].x - centerPoint[1].x) + (centerPoint[0].y - centerPoint[1].y)*(centerPoint[2].y - centerPoint[1].y); if (minMultiple > multiple) { minIndex = 1; minMultiple = multiple; } multiple = (centerPoint[0].x - centerPoint[2].x)*(centerPoint[1].x - centerPoint[2].x) + (centerPoint[0].y - centerPoint[2].y)*(centerPoint[1].y - centerPoint[2].y); if (minMultiple > multiple) { minIndex = 2; minMultiple = multiple; } return minIndex; }

6、确定另外两个点的次序关系 这一步主要是用于确定三个“回”中,除去直角的“回”,另外两个“回"的位置,之所以要确定是因为确定了之后才能完成完成二维码的矫正。
一个摆正的二维码是这样的:
Opencv的使用小教程|Opencv的使用小教程3——利用轮廓检测实现二维码定位
文章图片

左上角的“回”就是垂直角的“回”,右上角的“回”和左下角的“回”是不可以替换的,我们如果能够确定其次序关系,将有助于我们对二维码进行矫正定位。
如何确定这另外两个“回”的次序呢,在确定直角的时候我们使用了垂直定理,实际上求的是两个向量的内积,现在我们使用外积的公式。
什么是外积呢,大概是这样:
Opencv的使用小教程|Opencv的使用小教程3——利用轮廓检测实现二维码定位
文章图片

a外积b得到的向量就是朝上的,b外积a得到的向量与之相反,是朝下的。
在二维码上是怎么体现的呢?
Opencv的使用小教程|Opencv的使用小教程3——利用轮廓检测实现二维码定位
文章图片

在这里我定义了a、b两条直线,此时a外积b得到的向量就是电脑屏幕朝里的,b外积a得到的向量与之相反,是朝外的。
在实际使用时,我们假设a、b在同一平面,它们的z轴方向的分量都是0,因此他们外积的结果只会与z轴平行,此时我们便可以通过z轴上数值的正负判断两个点的次序了。(这里需要数学基础……我虽然懂但是真的讲不太来。)
具体计算公式如下:
Opencv的使用小教程|Opencv的使用小教程3——利用轮廓检测实现二维码定位
文章图片

具体实现代码为,otherIndex[0]就是右上角的“回”,otherIndex[1]就是左下角的“回”:
vector otherTwoPoint(vector centerPoint,int leftTopPointIndex) { vector otherIndex; double waiji = (centerPoint[(leftTopPointIndex + 1) % 3].x- centerPoint[(leftTopPointIndex) % 3].x)* (centerPoint[(leftTopPointIndex + 2) % 3].y - centerPoint[(leftTopPointIndex) % 3].y) - (centerPoint[(leftTopPointIndex + 2) % 3].x - centerPoint[(leftTopPointIndex) % 3].x)* (centerPoint[(leftTopPointIndex + 1) % 3].y - centerPoint[(leftTopPointIndex) % 3].y); if (waiji > 0) { otherIndex.push_back((leftTopPointIndex + 1) % 3); otherIndex.push_back((leftTopPointIndex + 2) % 3); } else { otherIndex.push_back((leftTopPointIndex + 2) % 3); otherIndex.push_back((leftTopPointIndex + 1) % 3); } return otherIndex; }

7、计算旋转角 计算旋转角需要借助三个“回”的点位来进行。
首先通过如下公式计算右上角的“回”与左上角的“回”形成的k值。
double dy = rightTopPoint.y - leftTopPoint.y; double dx = rightTopPoint.x - leftTopPoint.x; double k = dy / dx;

再利用以下公式可以计算对应的与opencv中x轴形成的角度:
double angle = atan(k) * 180 / CV_PI; //转化角度

Opencv的使用小教程|Opencv的使用小教程3——利用轮廓检测实现二维码定位
文章图片

此时θ角是负值。计算出该θ角就可以对图像进行矫正了,但是仅仅使用左上角和右上角可能会存在图像对称的问题,具体情况如下。
Opencv的使用小教程|Opencv的使用小教程3——利用轮廓检测实现二维码定位
文章图片

这两幅图的θ角是一样的,但是明显,其需要旋转的角度不同,左边那幅图旋转theta角之后就可以摆正,但是右边那幅图我们会得到如下结果。
Opencv的使用小教程|Opencv的使用小教程3——利用轮廓检测实现二维码定位
文章图片

很明显,需要再旋转180度才可以得到正确结果,通过对比我们发现,当左下角的“回”的y值小于左上角的“回”的y值时(opencv坐标系),会出现上述情况,因此,我们需要再判断左下角的“回”的y值和左上角的“回”的y值的关系。
if (leftBottomPoint.y < leftTopPoint.y) angle -= 180;

即可。
最后得到旋转角度的函数为:
double rotateAngle(Point leftTopPoint, Point rightTopPoint, Point leftBottomPoint) { double dy = rightTopPoint.y - leftTopPoint.y; double dx = rightTopPoint.x - leftTopPoint.x; double k = dy / dx; double angle = atan(k) * 180 / CV_PI; //转化角度 if (leftBottomPoint.y < leftTopPoint.y) angle -= 180; return angle; }

8、完成二维码的旋转 已经得到旋转的角度,之后便可以在原图中获得二维码的旋转结果,并截出二维码区域。
在完成二维码的截取时,需要给二维码截取函数传入三个参数,分别是,原图src,在第四步中得到的包含二维码的矩形rect,旋转的角度angle。
最终处理过程如下:
1、获得旋转中心;
2、获得需要抠图的范围,以旋转中心为中心。
3、通过包含二维码的矩形rect在src中截出尚未旋转的二维码。
4、按旋转中心,利用角度angle完成旋转
5、利用获得的需要抠图的范围进行抠图。
具体处理代码如下:
Mat transformQRcode(Mat src, RotatedRect rect,double angle) { // 获得旋转中心 Point center = rect.center; // 获得左上角和右下角的角点,而且要保证不超出图片范围,用于抠图 Point TopLeft = Point(cvRound(center.x), cvRound(center.y)) - Point(rect.size.height / 2, rect.size.width / 2); //旋转后的目标位置 TopLeft.x = TopLeft.x > src.cols ? src.cols : TopLeft.x; TopLeft.x = TopLeft.x < 0 ? 0 : TopLeft.x; TopLeft.y = TopLeft.y > src.rows ? src.rows : TopLeft.y; TopLeft.y = TopLeft.y < 0 ? 0 : TopLeft.y; int after_width, after_height; if (TopLeft.x + rect.size.width > src.cols) { after_width = src.cols - TopLeft.x - 1; } else { after_width = rect.size.width - 1; } if (TopLeft.y + rect.size.height > src.rows) { after_height = src.rows - TopLeft.y - 1; } else { after_height = rect.size.height - 1; } // 获得二维码的位置 Rect RoiRect = Rect(TopLeft.x, TopLeft.y, after_width, after_height); // dst是被旋转的图片,roi为输出图片,mask为掩模 Mat mask, roi, dst; Mat image; // 建立中介图像辅助处理图像 vector contour; // 获得矩形的四个点 Point2f points[4]; rect.points(points); for (int i = 0; i < 4; i++) contour.push_back(points[i]); vector contours; contours.push_back(contour); // 再中介图像中画出轮廓 drawContours(mask, contours, 0, Scalar(255,255,255), -1); // 通过mask掩膜将src中特定位置的像素拷贝到dst中。 src.copyTo(dst, mask); // 旋转 Mat M = getRotationMatrix2D(center, angle, 1); warpAffine(dst, image, M, src.size()); // 截图 roi = image(RoiRect); return roi; }

全部代码 这是二维码识别的全部代码,只要正确安装了opencv,都是可以正常运行的,这些代码是我参考了很多的blog和学习了很久的opencv才修改出来的,希望需要的人可以点个赞或者关注hahahah。
#include #include #include #include #include #include using namespace cv; using namespace std; // 用于矫正 Mat transformCorner(Mat src, RotatedRect rect); Mat transformQRcode(Mat src, RotatedRect rect, double angle); // 用于判断角点 bool IsQrPoint(vector& contour, Mat& img); bool isCorner(Mat &image); double Rate(Mat &count); int leftTopPoint(vector centerPoint); vector otherTwoPoint(vector centerPoint, int leftTopPointIndex); double rotateAngle(Point leftTopPoint, Point rightTopPoint, Point leftBottomPoint); //otherTwoPointIndex返回二维码对应的意义int main() { //VideoCapture cap; //Mat src; //cap.open(0); //打开相机,电脑自带摄像头一般编号为0,外接摄像头编号为1,主要是在设备管理器中查看自己摄像头的编号。 //cap.set(CV_CAP_PROP_FRAME_WIDTH, 1280); //设置捕获视频的宽度 //cap.set(CV_CAP_PROP_FRAME_HEIGHT, 400); //设置捕获视频的高度 //if (!cap.isOpened())//判断是否成功打开相机 //{ // cout << "摄像头打开失败!" << endl; //return -1; //} while (1) { Mat src; src = https://www.it610.com/article/imread("QRcode2.jpg"); //cap >> src; //从相机捕获一帧图像Mat srcCopy = src.clone(); //canvas为画布 将找到的定位特征画出来 Mat canvas; canvas = Mat::zeros(src.size(), CV_8UC3); Mat srcGray; //center_all获取特性中心 vector center_all; // 转化为灰度图 cvtColor(src, srcGray, COLOR_BGR2GRAY); // 3X3模糊 blur(srcGray, srcGray, Size(3, 3)); // 计算直方图 convertScaleAbs(src, src); equalizeHist(srcGray, srcGray); int s = srcGray.at(0, 0)[0]; // 设置阈值根据实际情况 如视图中已找不到特征 可适量调整 threshold(srcGray, srcGray, 0, 255, THRESH_BINARY | THRESH_OTSU); imshow("threshold", srcGray); /*contours是第一次寻找轮廓*/ /*contours2是筛选出的轮廓*/ vector contours; // 用于轮廓检测 vector hierarchy; findContours(srcGray, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE); // 小方块的数量 int numOfRec = 0; // 检测方块 int ic = 0; int parentIdx = -1; for (int i = 0; i < contours.size(); i++) { if (hierarchy[i][2] != -1 && ic == 0) { parentIdx = i; ic++; } else if (hierarchy[i][2] != -1) { ic++; } else if (hierarchy[i][2] == -1) { parentIdx = -1; ic = 0; } if (ic >= 2 && ic <= 2) { if (IsQrPoint(contours[parentIdx], src)) { RotatedRect rect = minAreaRect(Mat(contours[parentIdx])); // 画图部分 Point2f points[4]; rect.points(points); for (int j = 0; j < 4; j++) { line(src, points[j], points[(j + 1) % 4], Scalar(0, 255, 0), 2); } drawContours(canvas, contours, parentIdx, Scalar(0, 0, 255), -1); // 如果满足条件则存入 center_all.push_back(rect.center); numOfRec++; } ic = 0; parentIdx = -1; } }// 连接三个正方形的部分 for (int i = 0; i < center_all.size(); i++) { line(canvas, center_all[i], center_all[(i + 1) % center_all.size()], Scalar(255, 0, 0), 3); }vector contours3; Mat canvasGray; cvtColor(canvas, canvasGray, COLOR_BGR2GRAY); findContours(canvasGray, contours3, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE); vector maxContours; double maxArea = 0; // 在原图中画出二维码的区域for (int i = 0; i < contours3.size(); i++) { RotatedRect rect = minAreaRect(contours3[i]); Point2f boxpoint[4]; rect.points(boxpoint); for (int i = 0; i < 4; i++) line(src, boxpoint[i], boxpoint[(i + 1) % 4], Scalar(0, 0, 255), 3); double area = contourArea(contours3[i]); if (area > maxArea) { maxContours = contours3[i]; maxArea = area; } } imshow("src", src); if (numOfRec < 3) { waitKey(10); continue; } // 计算“回”的次序关系 int leftTopPointIndex = leftTopPoint(center_all); vector otherTwoPointIndex = otherTwoPoint(center_all, leftTopPointIndex); // canvas上标注三个“回”的次序关系 circle(canvas, center_all[leftTopPointIndex],10,Scalar(255,0,255),-1); circle(canvas, center_all[otherTwoPointIndex[0]], 10, Scalar(0, 255, 0),-1); circle(canvas, center_all[otherTwoPointIndex[1]], 10, Scalar(0, 255, 255),-1); // 计算旋转角 double angle = rotateAngle(center_all[leftTopPointIndex], center_all[otherTwoPointIndex[0]], center_all[otherTwoPointIndex[1]]); // 拿出之前得到的最大的轮廓 RotatedRect rect = minAreaRect(Mat(maxContours)); Mat image = transformQRcode(srcCopy, rect, angle); // 展示图像 imshow("QRcode", image); imshow("canvas", canvas); waitKey(10); } return 0; }Mat transformCorner(Mat src, RotatedRect rect) { // 获得旋转中心 Point center = rect.center; // 获得左上角和右下角的角点,而且要保证不超出图片范围,用于抠图 Point TopLeft = Point(cvRound(center.x), cvRound(center.y)) - Point(rect.size.height / 2, rect.size.width / 2); //旋转后的目标位置 TopLeft.x = TopLeft.x > src.cols ? src.cols : TopLeft.x; TopLeft.x = TopLeft.x < 0 ? 0 : TopLeft.x; TopLeft.y = TopLeft.y > src.rows ? src.rows : TopLeft.y; TopLeft.y = TopLeft.y < 0 ? 0 : TopLeft.y; int after_width, after_height; if (TopLeft.x + rect.size.width > src.cols) { after_width = src.cols - TopLeft.x - 1; } else { after_width = rect.size.width - 1; } if (TopLeft.y + rect.size.height > src.rows) { after_height = src.rows - TopLeft.y - 1; } else { after_height = rect.size.height - 1; } // 获得二维码的位置 Rect RoiRect = Rect(TopLeft.x, TopLeft.y, after_width, after_height); // dst是被旋转的图片 roi为输出图片 mask为掩模 double angle = rect.angle; Mat mask, roi, dst; Mat image; // 建立中介图像辅助处理图像 vector contour; // 获得矩形的四个点 Point2f points[4]; rect.points(points); for (int i = 0; i < 4; i++) contour.push_back(points[i]); vector contours; contours.push_back(contour); // 再中介图像中画出轮廓 drawContours(mask, contours, 0, Scalar(255, 255, 255), -1); // 通过mask掩膜将src中特定位置的像素拷贝到dst中。 src.copyTo(dst, mask); // 旋转 Mat M = getRotationMatrix2D(center, angle, 1); warpAffine(dst, image, M, src.size()); // 截图 roi = image(RoiRect); return roi; }// 该部分用于检测是否是角点,与下面两个函数配合 bool IsQrPoint(vector& contour, Mat& img) { double area = contourArea(contour); // 角点不可以太小 if (area < 30) return 0; RotatedRect rect = minAreaRect(Mat(contour)); double w = rect.size.width; double h = rect.size.height; double rate = min(w, h) / max(w, h); if (rate > 0.7) { // 返回旋转后的图片,用于把“回”摆正,便于处理 Mat image = transformCorner(img, rect); if (isCorner(image)) { return 1; } } return 0; }// 计算内部所有白色部分占全部的比率 double Rate(Mat &count) { int number = 0; int allpixel = 0; for (int row = 0; row < count.rows; row++) { for (int col = 0; col < count.cols; col++) { if (count.at(row, col) == 255) { number++; } allpixel++; } } //cout << (double)number / allpixel << endl; return (double)number / allpixel; }// 用于判断是否属于角上的正方形 bool isCorner(Mat &image) { // 定义mask Mat imgCopy, dstCopy; Mat dstGray; imgCopy = image.clone(); // 转化为灰度图像 cvtColor(image, dstGray, COLOR_BGR2GRAY); // 进行二值化 threshold(dstGray, dstGray, 0, 255, THRESH_BINARY | THRESH_OTSU); dstCopy = dstGray.clone(); //备份 // 找到轮廓与传递关系 vector contours; vector hierarchy; findContours(dstCopy, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE); for (int i = 0; i < contours.size(); i++) { //cout << i << endl; if (hierarchy[i][2] == -1 && hierarchy[i][3]) { Rect rect = boundingRect(Mat(contours[i])); rectangle(image, rect, Scalar(0, 0, 255), 2); // 最里面的矩形与最外面的矩形的对比 if (rect.width < imgCopy.cols * 2 / 7)//2/7是为了防止一些微小的仿射 continue; if (rect.height < imgCopy.rows * 2 / 7)//2/7是为了防止一些微小的仿射 continue; // 判断其中黑色与白色的部分的比例 if (Rate(dstGray) > 0.20) { return true; } } } returnfalse; }int leftTopPoint(vector centerPoint) { int minIndex = 0; int multiple = 0; int minMultiple = 10000; multiple = (centerPoint[1].x - centerPoint[0].x)*(centerPoint[2].x - centerPoint[0].x) + (centerPoint[1].y - centerPoint[0].y)*(centerPoint[2].y - centerPoint[0].y); if (minMultiple > multiple){ minIndex = 0; minMultiple = multiple; } multiple = (centerPoint[0].x - centerPoint[1].x)*(centerPoint[2].x - centerPoint[1].x) + (centerPoint[0].y - centerPoint[1].y)*(centerPoint[2].y - centerPoint[1].y); if (minMultiple > multiple) { minIndex = 1; minMultiple = multiple; } multiple = (centerPoint[0].x - centerPoint[2].x)*(centerPoint[1].x - centerPoint[2].x) + (centerPoint[0].y - centerPoint[2].y)*(centerPoint[1].y - centerPoint[2].y); if (minMultiple > multiple) { minIndex = 2; minMultiple = multiple; } return minIndex; }vector otherTwoPoint(vector centerPoint,int leftTopPointIndex) { vector otherIndex; double waiji = (centerPoint[(leftTopPointIndex + 1) % 3].x- centerPoint[(leftTopPointIndex) % 3].x)* (centerPoint[(leftTopPointIndex + 2) % 3].y - centerPoint[(leftTopPointIndex) % 3].y) - (centerPoint[(leftTopPointIndex + 2) % 3].x - centerPoint[(leftTopPointIndex) % 3].x)* (centerPoint[(leftTopPointIndex + 1) % 3].y - centerPoint[(leftTopPointIndex) % 3].y); if (waiji > 0) { otherIndex.push_back((leftTopPointIndex + 1) % 3); otherIndex.push_back((leftTopPointIndex + 2) % 3); } else { otherIndex.push_back((leftTopPointIndex + 2) % 3); otherIndex.push_back((leftTopPointIndex + 1) % 3); } return otherIndex; }double rotateAngle(Point leftTopPoint, Point rightTopPoint, Point leftBottomPoint) { double dy = rightTopPoint.y - leftTopPoint.y; double dx = rightTopPoint.x - leftTopPoint.x; double k = dy / dx; double angle = atan(k) * 180 / CV_PI; //转化角度 if (leftBottomPoint.y < leftTopPoint.y) angle -= 180; return angle; }Mat transformQRcode(Mat src, RotatedRect rect,double angle) { // 获得旋转中心 Point center = rect.center; // 获得左上角和右下角的角点,而且要保证不超出图片范围,用于抠图 Point TopLeft = Point(cvRound(center.x), cvRound(center.y)) - Point(rect.size.height / 2, rect.size.width / 2); //旋转后的目标位置 TopLeft.x = TopLeft.x > src.cols ? src.cols : TopLeft.x; TopLeft.x = TopLeft.x < 0 ? 0 : TopLeft.x; TopLeft.y = TopLeft.y > src.rows ? src.rows : TopLeft.y; TopLeft.y = TopLeft.y < 0 ? 0 : TopLeft.y; int after_width, after_height; if (TopLeft.x + rect.size.width > src.cols) { after_width = src.cols - TopLeft.x - 1; } else { after_width = rect.size.width - 1; } if (TopLeft.y + rect.size.height > src.rows) { after_height = src.rows - TopLeft.y - 1; } else { after_height = rect.size.height - 1; } // 获得二维码的位置 Rect RoiRect = Rect(TopLeft.x, TopLeft.y, after_width, after_height); // dst是被旋转的图片,roi为输出图片,mask为掩模 Mat mask, roi, dst; Mat image; // 建立中介图像辅助处理图像 vector contour; // 获得矩形的四个点 Point2f points[4]; rect.points(points); for (int i = 0; i < 4; i++) contour.push_back(points[i]); vector contours; contours.push_back(contour); // 再中介图像中画出轮廓 drawContours(mask, contours, 0, Scalar(255,255,255), -1); // 通过mask掩膜将src中特定位置的像素拷贝到dst中。 src.copyTo(dst, mask); // 旋转 Mat M = getRotationMatrix2D(center, angle, 1); warpAffine(dst, image, M, src.size()); // 截图 roi = image(RoiRect); return roi; }

如果有不懂的地方欢迎大家询问。

    推荐阅读