最小外接矩形(不完美)

前言

本章节结合前面一章的批处理函数glob(),进行图像的最小外接矩形的描绘(实际效果不太完美)。

测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <iostream>
#include <string>
#include <opencv2/opencv.hpp>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
using namespace std;
using namespace cv;

void minRect(Mat& srcImage);
int main(){
string path = "/Users/cezarbao/Desktop/DeepLearning/OpenCV/*.jpeg";
vector<Mat>images;
vector<String>srcImages;
glob(path,srcImages,false);
size_t cnt = srcImages.size();
for(int i = 0; i < cnt; i++)
{
images.push_back(imread(srcImages[i]));
minRect(images[i]);
}
}
void minRect(Mat& srcImg){
Mat dstImage = srcImg.clone();
cvtColor(srcImg, srcImg, COLOR_BGR2GRAY);
threshold(srcImg, srcImg, 100, 255, THRESH_BINARY);
vector<vector<Point>> contours;
vector<Vec4i> hierarcy;
findContours(srcImg, contours, hierarcy, RETR_EXTERNAL, CHAIN_APPROX_NONE);
vector<RotatedRect> box(contours.size()); //定义最小外接矩形集合
Point2f rect[4];
for(int i=0; i<contours.size(); i++)
{
box[i] = minAreaRect(Mat(contours[i])); //计算每个轮廓最小外接矩形
box[i].points(rect); //把最小外接矩形四个端点复制给rect数组
for(int j=0; j<4; j++)
{
line(dstImage, rect[j], rect[(j+1)%4], Scalar(0, 0, 255), 2, LINE_AA); //绘制最小外接矩形每条边
}
}
imshow("Image",dstImage);
waitKey(0);
}

效果图:
(可以看到第二张图明显识别出了问题……)



一、findContours()轮廓扫描函数

函数原型:

1
2
3
4
5
6
7
8
findContours( 
InputOutputArray image, 
OutputArrayOfArrays contours,           
OutputArray hierarchy, 
int mode,            
int method
Point offset=Point()
)

第一个参数是输入图像,这里有很大一个坑,输入图像必须是八位单通道图像(即8UC1),在函数中被认为是一个二值化图像(即所有非零元素都被视作是相等的,非0即1),但如果mode是CV_RETR_CCOMP 或者 CV_RETR_FLOODFILL,输入图像也可以是32位的整型图像(CV_32SC1)。
第二个参数是二维vector数组,这里将使用找到的轮廓的列表进行填充(即,这将是一个contours的vector,其中contours[i]表示一个特定的轮廓,这样,contours[i][j]将表示contour[i]的一个特定的端点)。
第三个参数可以指定,也可以不提指定。如果指定的话,输出hierarchy,将会描述输出轮廓树的结构信息。(Vec4i是Vec<int,4>的别名,定义了一个“向量内每一个元素包含了4个int型变量”的向量)
第四个参数将会告诉OpenCV你想用何种方式来对轮廓进行提取
RETR_EXTERNAL:表示只提取最外面的轮廓;
RETR_LIST:表示提取所有轮廓并将其放入列表;
RETR_CCOMP:表示提取所有轮廓并将组织成一个两层结构,其中顶层轮廓是外部轮廓,第二层轮廓是“洞”的轮廓;
RETR_TREE:表示提取所有轮廓并组织成轮廓嵌套的完整层级结构。
第五个参数给出轮廓如何呈现的方法
CHAIN_APPROX_NONE:将轮廓中的所有点的编码转换成点;
CHAIN_APPROX_SIMPLE:压缩水平、垂直和对角直线段,仅保留它们的端点;

CHAIN_APPROX_TC89_L1 or CHAIN_APPROX_TC89_KCOS:应用Teh-Chin链近似算法中的一种风格

二、minAreaRect()函数

函数原型:

1
RotatedRect minAreaRect(InputArray points)

第一个参数可以输入点阵容器(vector)或者Mat类型的图像,这里很坑的是,如果参数是Mat类型必须满足depth == CV_32F || depth == CV_32S,且checkVector(2)才可以,否则会报错(minAreaRect()中主要调用的求凸包的函数convexHull()会检查Mat满不满足上面的条件)。
Mat::depth()函数:求矩阵中元素的一个通道的数据类型,这个值和type是相关的。
Mat::checkVector()函数:当Mat的channels,depth,和连续性 满足checkVector的参数内容时,返回(int)(total()*channels()/_elemChannels), 否则返回-1。checkVector(2),要求矩阵的列数位2。
(我就是因为minAreaRect()中的Mat类总是报错才怒转findContours()函数的,然而测试代码的视线效果并不完美,有待改进)

三、RotatedRect类

RotatedRect类常用于配合minAreaRect()函数的计算(因为minAreaRect()函数的返回类型就是RotatedRect类)
函数定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class CV_EXPORTS RotatedRect
{
public:
//! various constructors
RotatedRect();
RotatedRect(const Point2f& center, const Size2f& size, float angle);
RotatedRect(const CvBox2D& box);
//! returns 4 vertices of the rectangle
void points(Point2f pts[]) const;
//! returns the minimal up-right rectangle containing the rotated rectangle
Rect boundingRect() const;
//! conversion to the old-style CvBox2D structure
operator CvBox2D() const;
Point2f center; //< the rectangle mass center
Size2f size; //< width and height of the rectangle
float angle; //< the rotation angle. When the angle is 0, 90, 180, 270 etc., the rectangle becomes an up-right rectangle.
};

RotatedRect类中定义了矩形的中心点center、尺寸size(包括width、height)、旋转角度angle共3个成员变量;
points()函数用于求矩形的4个顶点,boundingRect()函数求包含最小外接矩形的,与坐标轴平行(或垂直)的最小矩形。
参考博文: