我的个人博客:谋仁·Blog
微信公众号:谋仁的麻袋
CSDN:曹谋仁
所谓形状/轮廓的检测就是把待检测图像中的边缘轮廓组成的图形识别出来,并检测出轮廓点,再对其进行判断。所以为便于检测轮廓,我们要在检测之前进行一些预处理:
灰度->高斯滤波->Canny边缘检测算法->图像膨胀
代码:
///
/// 形状检测前的预处理(灰度->高斯滤波->Canny边缘算法->膨胀)
///
/// Mat 类,输入图像
/// Mat类,预处理后的图像
Mat preProcessing(Mat imgIn) {
Mat imgGray, imgBlur, imgCanny, imgDila;
//先定义一个内核
Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3));
cvtColor(imgIn, imgGray, COLOR_BGR2GRAY, 0);//灰度变换
GaussianBlur(imgGray, imgBlur, Size(65, 65), 1, 1);//高斯滤波去噪点
Canny(imgBlur, imgCanny, 40, 120);//Canny边缘检测
dilate(imgCanny, imgDila, kernel);//图像膨胀
return imgDila;//返回处理完后的图像
}
函数作用:从二值图像中检测轮廓
函数的定义:
void findContours(
InputArray image,
OutputArrayOfArrays contours,
OutputArray hierarchy,
int mode,
int method,
Point offset = Point()
);
参数解释:
vector<vector<Point>>
这是一个双重向量,外层向量存放待检测图中所有形状的轮廓;内层向量存放的是某一个轮廓的点集合(一组由连续的Point构成的点的集合的向量)
vector<Vec4i> hierarchy
Vec4i的定义:
typedef Vec<int, 4> Vec4i
向量内每个元素都包含了4个int型变量,所以从定义上看,hierarchy是一个向量,向量内每个元素都是一个包含4个int型的数组。
向量hierarchy内的元素和轮廓向量contours内的元素是一一对应的,向量的容量相同。hierarchy内每个元素的4个int型变量是hierarchy[i][0] ~ hierarchy[i][3],分别表示当前轮廓 i 的后一个轮廓、前一个轮廓、父轮廓和内嵌轮廓的编号索引。如果当前轮廓没有对应的四者中的其一的话,则相应的hierarchy[i][*]被置为-1。
enum RetrievalModes {
/** retrieves only the extreme outer contours. It sets `hierarchy[i][2]=hierarchy[i][3]=-1` for
all the contours. */
RETR_EXTERNAL = 0,
/** retrieves all of the contours without establishing any hierarchical relationships. */
RETR_LIST = 1,
/** retrieves all of the contours and organizes them into a two-level hierarchy. At the top
level, there are external boundaries of the components. At the second level, there are
boundaries of the holes. If there is another contour inside a hole of a connected component, it
is still put at the top level. */
RETR_CCOMP = 2,
/** retrieves all of the contours and reconstructs a full hierarchy of nested contours.*/
RETR_TREE = 3,
RETR_FLOODFILL = 4 //!<
};
翻译如下:
RETR_EXTERNAL:只检测最外围轮廓,包含在外围轮廓内的内围轮廓被忽略;
RETR_LIST:检测所有的轮廓,包括内围、外围轮廓,但是检测到的轮廓不建立等级关系,彼此之间独立,没有等级关系。这就意味着这个检索模式下不存在父轮廓或内嵌轮廓,所以hierarchy向量内所有元素的第3、第4个分量都会被置为-1;
RETR_CCOMP:检测所有的轮廓,但所有轮廓只建立两个等级关系,外围为顶层,若外围内的内围轮廓还包含了其他的轮廓信息,则内围内的所有轮廓均归属于顶层;
RETR_TREE:检测所有轮廓,所有轮廓建立一个等级树结构。外层轮廓包含内层轮廓,内层轮廓还可以继续包含内嵌轮廓。
RETR_FLOODFILL:官方没有介绍,暂时不知道
参考:https://www.cnblogs.com/GaloisY/p/11062065.html
enum ContourApproximationModes {
/** stores absolutely all the contour points. That is, any 2 subsequent points (x1,y1) and
(x2,y2) of the contour will be either horizontal, vertical or diagonal neighbors, that is,
max(abs(x1-x2),abs(y2-y1))==1. */
CHAIN_APPROX_NONE = 1,
/** compresses horizontal, vertical, and diagonal segments and leaves only their end points.
For example, an up-right rectangular contour is encoded with 4 points. */
CHAIN_APPROX_SIMPLE = 2,
/** applies one of the flavors of the Teh-Chin chain approximation algorithm @cite TehChin89 */
CHAIN_APPROX_TC89_L1 = 3,
/** applies one of the flavors of the Teh-Chin chain approximation algorithm @cite TehChin89 */
CHAIN_APPROX_TC89_KCOS = 4
};
翻译如下:
CHAIN_APPROX_NONE:获取每个轮廓的每个像素,相邻的两个点的像素位置差不超过1;
CHAIN_APPROX_SIMPLE:压缩水平方向,垂直方向,对角线方向的元素,值保留该方向的重点坐标,如果一个矩形轮廓只需4个点来保存轮廓信息;
CHAIN_APPROX_TC89_L1和CHAIN_APPROX_TC89_KCOS:使用Teh-Chinl链逼近算法中的一种
参考:https://blog.csdn.net/qq_42887760/article/details/86565601
findContours函数另一个重载与该定义唯一的区别是少了hierarchy参数,其他参数及含义都一样,故在此不多赘述。
函数作用:计算图像轮廓的面积
函数的定义:
double contourArea( InputArray contour, bool oriented = false );
参数解释:
函数作用:计算图像轮廓的周长
函数的定义:
double arcLength( InputArray curve, bool closed );
参数解释:
函数作用:将曲线或多边形用更少顶点的折现来拟合,即把一个连续光滑曲线折线化
曲线折线化的目的是获得折线顶点个数,进而可以判断出该轮廓大概是什么形状
函数的定义:
void approxPolyDP(
InputArray curve,
OutputArray approxCurve,
double epsilon,
bool closed
);
参数解释:
函数作用:绘制已经找到的图像的轮廓
函数的定义:
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()
);
参数解释:
enum LineTypes {
FILLED = -1,
LINE_4 = 4, //!< 4-connected line--4连通线型
LINE_8 = 8, //!< 8-connected line--8连通线型
LINE_AA = 16 //!< antialiased line--抗锯齿线型
};
源代码:
#include
#include
using namespace cv;
using namespace std;
#ifdef _DEBUG
#pragma comment(lib,"opencv_world453d.lib")
#else
#pragma comment(lib,"opencv_world453.lib")
#endif // _DEBUG
///
/// 对预处理后的图像检测轮廓并标出已定义的简单形状
///
/// 与处理后的图像
/// 待检测轮廓的原图
void GetContour(Mat imgDil, Mat img)
{
vector<vector<Point>> contour;
vector<Vec4i> hierarchy;
findContours(imgDil, contour, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_NONE);
vector<vector<Point>> conPoly(contour.size());//定义用于存放轮廓折线化后的变量
vector<Rect> boundRect(contour.size());
//contour.size()是在该图像中检测到的轮廓个数
for (int i = 0; i < contour.size(); i++)//所有轮廓遍历
{
string objectType;//形状名称
float peri = arcLength(contour[i], true);//轮廓周长
//0.015* peri表示拟合的精度
approxPolyDP(contour[i], conPoly[i], 0.015 * peri, true);//把一个连续光滑曲线折线化
boundRect[i] = boundingRect(conPoly[i]);//获得包覆该轮廓最小矩形
int objCor = (int)conPoly[i].size();//获取折线化后轮廓的顶点个数
#pragma region 定义形状
if (objCor == 3) //三角形
objectType = "Tri";
else if (objCor == 4) {//矩形
float aspRatio = (float)boundRect[i].width / boundRect[i].height;//长宽比来区别正方形与矩形
if (aspRatio > 0.95 && aspRatio < 1.05)
objectType = "Square";
else
objectType = "Rect";
}
else if (objCor > 4) //圆形
objectType = "Circle";
#pragma endregion
drawContours(img, conPoly, i, Scalar(187, 109, 68), 3, LINE_AA);//画出轮廓线
rectangle(img, boundRect[i].tl(), boundRect[i].br(), Scalar(72,255,104), 4);//用矩形框住选出的图形
//标出已定义出的图形名称
putText(img, objectType, { boundRect[i].x,boundRect[i].y - 5 } , 1, FONT_HERSHEY_PLAIN, Scalar(0, 69, 255), 1);
}
}
///
/// 形状检测前的预处理(灰度->高斯滤波->Canny边缘算法->膨胀)
///
/// Mat 类,输入图像
/// Mat类,预处理后的图像
Mat PreProcessing(Mat imgIn) {
Mat imgGray, imgBlur, imgCanny, imgDila;
//先定义一个内核
Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3));
cvtColor(imgIn, imgGray, COLOR_BGR2GRAY, 0);//灰度变换
GaussianBlur(imgGray, imgBlur, Size(65, 65), 1, 1);//高斯滤波去噪点
Canny(imgBlur, imgCanny, 40, 120);//Canny边缘检测
dilate(imgCanny, imgDila, kernel);//图像膨胀
return imgDila;//返回处理完后的图像
}
void main() {
string path = "D:\\My Bags\\图片\\shapes.png";//原图路径
Mat img = imread(path);//读取图片
Mat imgProcessed = PreProcessing(img);//图像预处理
GetContour(imgProcessed , img);//检测轮廓并标出简单图形名称
imshow("Image", img);//显示出来
waitKey(0);
}