温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

怎么利用OpenCV提取图像中的矩形区域

发布时间:2021-06-11 14:36:00 来源:亿速云 阅读:650 作者:小新 栏目:开发技术

这篇文章主要介绍了怎么利用OpenCV提取图像中的矩形区域,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。

分析问题

照片中的PPT区域总是沿着x,y,z三个轴都有倾斜(如下图),要想把照片翻转到平行位置,需要进行透视变换,而透视变换需要同一像素点变换前后的坐标。由此可以想到,提取矩形区域四个角的坐标作为变换前的坐标,变换后的坐标可以设为照片的四个角落,经过投影变换,矩形区域将会翻转并充满图像。

怎么利用OpenCV提取图像中的矩形区域

因此我们要解决的问题变为:提取矩形的四个角落、进行透视变换。

提取矩形角落坐标

矩形的检测主要是提取边缘,PPT显示部分的亮度通常高于周围环境,我们可以将图片阈值化,将PPT部分与周围环境明显的分别开来,这对后边的边缘检测非常有帮助。

检测矩形并提取坐标需要对图像进行预处理、边缘检测、提取轮廓、检测凸包、角点检测。

预处理

由于手机拍摄的照片像素可能会很高,为了加快处理速度,我们首先缩小图片,这里缩小了4倍。

pyrDown(srcPic,   shrinkedPic);    //减小尺寸 加快运算速度
pyrDown(shrinkedPic, shrinkedPic);

转化为灰度图

cvtColor(shrinkedPic, greyPic, COLOR_BGR2GRAY); //转化为灰度图

中值滤波

medianBlur(greyPic, greyPic, 7); //中值滤波

转为二值图片

threshold(greyPic, binPic, 80, 255, THRESH_BINARY); //阈值化为二值图片

此时图片已经变成了这个样子:

怎么利用OpenCV提取图像中的矩形区域

可见PPT部分已经与环境分离开来。

边缘检测与轮廓处理

进行Canny边缘检测

Canny(binPic, cannyPic, cannyThr, cannyThr*FACTOR); //Canny边缘检测

这里 cannyThr = 200, FACTOR = 2.5
可能由于边缘特征过于明显,系数在100-600范围(具体数字可能有出入,反正范围非常大)内产生的效果几乎相同。

提取轮廓

vector<vector<Point>> contours;  //储存轮廓
vector<Vec4i> hierarchy;
  
findContours(cannyPic, contours, hierarchy, RETR_CCOMP, CHAIN_APPROX_SIMPLE);  //获取轮廓

findContour函数原型如下:

CV_EXPORTS_W void findContours( InputOutputArray image, OutputArrayOfArrays contours,
               OutputArray hierarchy, int mode,
              int method, Point offset = Point());

检测到的轮廓都存在contours里,每个轮廓保存为一个vector<Point>
hierarchy为可选的输出向量,包括图像的拓扑信息,这里可以选择不用。

我们可以反复调用drawContours函数将轮廓画出

linePic = Mat::zeros(cannyPic.rows, cannyPic.cols, CV_8UC3);
for (int index = 0; index < contours.size(); index++){    
    drawContours(linePic, contours, index, Scalar(rand() & 255, rand() & 255, rand() & 255), 1, 8/*, hierarchy*/);
}

drawContours函数原型:

CV_EXPORTS_W void drawContours( InputOutputArray image, InputArrayOfArrays contours,
              int contourIdx, const Scalar& color,
              int thickness = 1, int lineType = LINE_8,
              InputArray hierarchy = noArray(),
              int maxLevel = INT_MAX, Point offset = Point() );

作用是将contours中的第contourIdx条轮廓用color颜色绘制到image中,thickness为线条的粗细, contourIdx为负数时画出所有轮廓

这里要注意的是在绘制轮廓前要提前为输出矩阵分配空间,否则会出现以下错误

OpenCV(3.4.1) Error: Assertion failed (size.width>0 && size.height>0) in cv::imshow, file C:\build\master_winpack-build-win64-vc15\opencv\modules\highgui\src\window.cpp, line 356

怎么利用OpenCV提取图像中的矩形区域

提取面积最大的轮廓并用多边形将轮廓包围

从上面的轮廓图中看出,PPT的矩形已经成为了图片的主要部分,接下来的思路是提取面积最大的轮廓,得到矩形轮廓。

vector<vector<Point>> polyContours(contours.size());
int maxArea = 0;
for (int index = 0; index < contours.size(); index++){    
    if (contourArea(contours[index]) > contourArea(contours[maxArea]))
      maxArea = index;    
    approxPolyDP(contours[index], polyContours[index], 10, true);
  }

contourArea用来计算轮廓的面积
approxPolyDP的作用是用多边形包围轮廓,可以得到严格的矩形,有助于找到角点

画出矩形,同样注意要提前为Mat分配空间

Mat polyPic = Mat::zeros(shrinkedPic.size(), CV_8UC3);
drawContours(polyPic, polyContours, maxArea, Scalar(0,0,255/*rand() & 255, rand() & 255, rand() & 255*/), 2);

怎么利用OpenCV提取图像中的矩形区域

如图,接下来我们只需提取到四个角的坐标

寻找凸包

vector<int> hull;
convexHull(polyContours[maxArea], hull, false);  //检测该轮廓的凸包

convexHull函数原型

CV_EXPORTS_W void convexHull( InputArray points, OutputArray hull,
              bool clockwise = false, bool returnPoints = true );

hull为输出参数, clockwise决定凸包顺逆时针方向, returnPoints为真时返回凸包的各个点,否则返回各点的指数
hull可以为vector<int>类型,此时返回的是凸包点在原图中的下标索引

我们可以把点和多边形添加到原图中查看效果

for (int i = 0; i < hull.size(); ++i){
    circle(polyPic, polyContours[maxArea][i], 10, Scalar(rand() & 255, rand() & 255, rand() & 255), 3);
  }
addWeighted(polyPic, 0.5, shrinkedPic, 0.5, 0, shrinkedPic);

怎么利用OpenCV提取图像中的矩形区域

现在我们已经比较准确地获得了需要的点,下面就要利用这些点进行坐标映射。

投影变换

投影变换需要像素在两个坐标系中的坐标一一对应,虽然我们已经有了四个坐标,但还没有区分它们的位置。

新建两个数组

Point2f srcPoints[4], dstPoints[4];
dstPoints[0] = Point2f(0, 0);
dstPoints[1] = Point2f(srcPic.cols, 0);
dstPoints[2] = Point2f(srcPic.cols, srcPic.rows);
dstPoints[3] = Point2f(0, srcPic.rows);

dstPoints储存的是变换后各点的坐标,依次为左上,右上,右下, 左下

srcPoints储存的是上面得到的四个角的坐标

下面对得到的四个点进行处理

for (int i = 0; i < 4; i++){
  polyContours[maxArea][i] = Point2f(polyContours[maxArea][i].x * 4, polyContours[maxArea][i].y * 4); //恢复坐标到原图
}
    //对四个点进行排序 分出左上 右上 右下 左下
bool sorted = false;
int n = 4;
while (!sorted){
  for (int i = 1; i < n; i++){
  sorted = true;
    if (polyContours[maxArea][i-1].x > polyContours[maxArea][i].x){
      swap(polyContours[maxArea][i-1], polyContours[maxArea][i]);
      sorted = false;
    }
  }
  n--;
}
if (polyContours[maxArea][0].y < polyContours[maxArea][1].y){
  srcPoints[0] = polyContours[maxArea][0];
  srcPoints[3] = polyContours[maxArea][1];
}
else{
  srcPoints[0] = polyContours[maxArea][1];
  srcPoints[3] = polyContours[maxArea][0];
}

if (polyContours[maxArea][9].y < polyContours[maxArea][10].y){
  srcPoints[1] = polyContours[maxArea][2];
  srcPoints[2] = polyContours[maxArea][3];
}
else{
  srcPoints[1] = polyContours[maxArea][3];
  srcPoints[2] = polyContours[maxArea][2];
}

即先对四个点的x坐标进行冒泡排序分出左右,再根据两对坐标的y值比较分出上下
(笔者试图通过凸包的顺逆时针顺序以及凸包点与原点的距离来活得位置信息,却均以失败告终)

坐标变换需要矩阵运算,OpenCV中给我们提供了getPerspectiveTransform函数用来得到矩阵

Mat transMat = getPerspectiveTransform(srcPoints, dstPoints); //得到变换矩阵

接下来进行坐标变换,网上查到的步骤都是通过perspectiveTransform函数变换,但尝试多次都出现了报错,Google了好长时间才知道原来这个函数的传入输入输出参数均为点集,我们这个场景用起来比较麻烦。

warpPerspective函数可以直接传入输入Mat类型数据,比较方便

warpPerspective(srcPic, outPic, transMat, srcPic.size()); //进行坐标变换

参数分别为输入输出图像、变换矩阵、大小。

坐标变换后就得到了我们要的最终图像。

怎么利用OpenCV提取图像中的矩形区域

感谢你能够认真阅读完这篇文章,希望小编分享的“怎么利用OpenCV提取图像中的矩形区域”这篇文章对大家有帮助,同时也希望大家多多支持亿速云,关注亿速云行业资讯频道,更多相关知识等着你来学习!

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI