索引目录
1.轮廓的属性
二值图像分析最常见的一个主要方式就是轮廓发现与轮廓分析,其中轮廓发现的目的是为轮廓分析做准备,经过轮廓分析我们可以得到轮廓各种有用的属性信息、常见的如下:
轮廓面积
轮廓周长
轮廓几何矩
轮廓的最小外接矩形
轮廓的最大外接矩形
轮廓的最小外接圆
轮廓的最小外接三角形
轮廓拟合(支持拟合直线、椭圆、圆)
轮廓的凸包
轮廓层次信息提取
多边形逼近
计算欧拉数
2.轮廓查找并绘制
轮廓检测的基本步骤:
1.图像转换为灰度图像,
2.图像的滤波,降噪,
3.使用compare、inRange、threshold、adaptiveThreshold、Canny等来创建灰度或彩色的二值图像
4.寻找轮廓findContours
5.绘制轮廓drawContours
轮廓查找函数,用于形状分析以及对象检测和识别
void cv::findContours(
InputArray image,// 输入图像、八位单通道的,背景为黑色
OutputArrayOfArrays contours,//得到的轮廓图像,是一系列的点集合vector<vector<Point>>
OutputArray hierarchy,//层次图像,根据需要提取轮廓层次信息,每个轮廓有四个相关信息,vector<Vec4i>
//分别是同层下一个、前一个、第一个子节点、父节点
int mode,
int method,
Point offset = Point()//表示轮廓偏移,默认为0
)
void findContours(
InputArray image,
OutputArrayOfArrays contours,
int mode,
int method,
Point offset = Point()
);
image原图像,一个8位的单通道图像。非零像素被认为为1,0像素为任然保留为0。所以图像被当做二值图像。您可以使用compare、inRange、threshold、adaptiveThreshold、Canny等来创建灰度或彩色的二值图像。如果mode等于RETR_CCOMP或RETR_FLOODFILL,则输入也可以是32位整数的标签图像(CV_32SC1)。
mode 表示轮廓寻找时候的拓扑结构返回
-RETR_EXTERNAL表示只返回最外层轮廓,包含在外围轮廓内的内围轮廓被忽略。
-RETR_LIST检测所有的轮廓,包括内围、外围轮廓,但是检测到的轮廓不建立等级关系,彼此之间独立,没有等级关系,这就意味着这个检索模式下不存在父轮廓或内嵌轮廓。
-RETR_CCOMP检索所有轮廓并将其组织为两级层次结构。在顶层,组件具有外部边界。在第二层,有孔的边界。如果所连接零部件的孔内还有其他轮廓,则该轮廓仍将放置在顶层。
-RETR_TREE表示检索所有轮廓,并重建嵌套轮廓的完整层次。所有轮廓建立一个等级树结构,外层轮廓包含内层轮廓,内层轮廓还可以继续包含内嵌轮廓。
Method表示轮廓点集合取得是基于什么算法,常见的是基于CHAIN_APPROX_SIMPLE链式编码方法
①:CHAIN_APPROX_NONE
绝对存储所有轮廓点。也就是说,轮廓的任意两个后续点(x1,y1)和(x2,y2)将是水平,垂直或对角线邻居,即相邻的两个点的像素位置差不超过1,即max (abs (x1 - x2), abs(y2 - y1) =1。
②:CHAIN_APPROX_SIMPLE
压缩水平,垂直和对角线段,仅保留其端点。例如,例如一个矩形轮廓只需4个点来保存轮廓信息。
③:CHAIN_APPROX_TC89_L1
使用teh-Chinl chain 近似算法
④:CHAIN_APPROX_TC89_KCOS
应用teh-Chinl chain 近似算法
绘制轮廓函数,绘制轮廓或者填充轮廓
void cv::drawContours(
InputOutputArray image,
InputArrayOfArrays contours,
int contourIdx,//绘制轮廓:-1代表绘制所有轮廓
const Scalar & color,
int thickness = 1,//正数的时候表示绘制该轮廓,负数的时候表示填充该轮廓
int lineType = LINE_8,
InputArray hierarchy = noArray(),
int maxLevel = INT_MAX,
Point offset = Point()
)
hierarchy 表示绘制轮廓的最大级别。 如果为0,则仅绘制指定的轮廓。 如果为1,则该函数绘制轮廓和所有嵌套轮廓。 如果为2,则该函数绘制轮廓,所有嵌套轮廓,所有嵌套到嵌套的轮廓,等等。 仅当有可用的层次结构时才考虑此参数
// 轮廓查找与绘制
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
findContours(binary, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point());
for (size_t t = 0; t < contours.size(); t++)
{
drawContours(src, contours, t, Scalar(0, 0, 255), 2, 8);
}
3.轮廓分析
3.1 常用API函数
// 计算轮廓面积
double cv::contourArea(
InputArray contour,
bool oriented = false
)
// 计算轮廓周长
double cv::arcLength(
InputArray curve,
bool closed
)
// 计算最小外接矩形
RotatedRect cv::minAreaRect(
InputArray points
)
// 计算最大外接矩形
Rect cv::boundingRect(
InputArray array
)
// 计算最小外接圆/拟合圆
void cv::minEnclosingCircle(
InputArray points,
Point2f & center,
float & radius
)
// 计算最小外接三角形/拟合三角形
double cv::minEnclosingTriangle(
InputArray points,
OutputArray triangle
)
// 拟合直线
void cv::fitLine(
InputArray points,
OutputArray line,
int distType,
double param,
double reps,
double aeps
)
// 最小二乘法拟合椭圆
RotatedRect cv::fitEllipse(
InputArray points
)
// 计算凸包
void cv::convexHull(
InputArray points,
OutputArray hull,
bool clockwise = false,
bool returnPoints = true
)
//判断一个轮廓是否为凸包,用于检测点是否落在多边形内之前的快速检测
bool cv::isContourConvex(
InputArray contour
)
// 多边形逼近-逼近真实形状
void cv::approxPolyDP(
InputArray curve,
OutputArray approxCurve,
double epsilon,
bool closed
)
//检测点是否落在多边形内
double cv::pointPolygonTest(
InputArray contour,
Point2f pt,
bool measureDist //True,则返回每个点到轮廓的距离,如果是False则返回+1,0,-1三个值,其中+1表示点在轮廓内部,0表示点在轮廓上,-1表示点在轮廓外
)
3.2 DEMO
//1. 轮廓的面积和弧长
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
findContours(binary, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE, Point());
for (size_t t = 0; t < contours.size(); t++) {
// 最大外接轮廓
Rect rect = boundingRect(contours[t]);
// rectangle(src, rect, Scalar(0, 0, 255), 1, 8, 0);
// 面积过滤
double area = contourArea(contours[t]);
double curvelen = arcLength(contours[t], true);
if (area < 100 || curvelen < 100) {
continue;
}
// 最小外接轮廓
RotatedRect rrt = minAreaRect(contours[t]);
Point2f pts[4];
rrt.points(pts);//RotatedRect 矩形的四个角点坐标
// 绘制旋转矩形与中心位置
for (int i = 0; i < 4; i++) {
line(src, pts[i % 4], pts[(i + 1) % 4], Scalar(0, 255, 0), 2, 8, 0);
}
Point2f cpt = rrt.center;
circle(src, cpt, 2, Scalar(255, 0, 0), 2, 8, 0);
}
//2.多边形逼近-逼近真实形状
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
findContours(binary, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE, Point());
Scalar color = Scalar(255, 0, 0);
for (size_t t = 0; t < contours.size(); t++) {
RotatedRect rrt = minAreaRect(contours[t]);
Point2f cpt = rrt.center;
circle(src, cpt, 2, Scalar(0, 255, 0), 2, 8, 0);
Mat result;
approxPolyDP(contours[t], result, 4, true);
printf("corners : %d\n", result.rows);
if (result.rows == 6) {
//在图上添加文字
putText(src, "poly", cpt, FONT_HERSHEY_SIMPLEX, .7, color, 1, 8);
}
if (result.rows == 3) {
putText(src, "triangle", cpt, FONT_HERSHEY_SIMPLEX, .7, color, 1, 8);
}
if (result.rows == 4) {
putText(src, "rectangle", cpt, FONT_HERSHEY_SIMPLEX, .7, color, 1, 8);
}
if(result.rows > 10) {
putText(src, "circle", cpt, FONT_HERSHEY_SIMPLEX, .7, Scalar(0, 255, 0), 1, 8);
}
}
//3. 直线拟合
Vec4f oneline;
fitLine(contours[t], oneline, DIST_L1, 0, 0.01, 0.01);
float vx = oneline[0];
float vy = oneline[1];
float x0 = oneline[2];
float y0 = oneline[3];
// 直线参数斜率k与截矩b
float k = vy / vx;
float b = y0 - k*x0;
//4.椭圆或圆拟合
RotatedRect rrt = fitEllipse(contours[t]);
float w = rrt.size.width;
float h = rrt.size.height;
Point centers = rrt.center;
ellipse(src, rrt, Scalar(0, 0, 255), 2, 8);
//5.绘制最大内接圆
#include "opencv2/highgui.hpp"
#include "opencv2/imgproc.hpp"
#include <iostream>
using namespace cv;
using namespace std;
int main(void)
{
//点多边形绘制
const int r = 100;
Mat src = Mat::zeros(Size(4 * r, 4 * r), CV_8U);
vector<Point2f> vert(6);
vert[0] = Point(3 * r / 2, static_cast<int>(1.34*r));
vert[1] = Point(1 * r, 2 * r);
vert[2] = Point(3 * r / 2, static_cast<int>(2.866*r));
vert[3] = Point(5 * r / 2, static_cast<int>(2.866*r));
vert[4] = Point(3 * r, 2 * r);
vert[5] = Point(5 * r / 2, static_cast<int>(1.34*r));
for (int i = 0; i < 6; i++)
{
line(src, vert[i], vert[(i + 1) % 6], Scalar(255), 3);
}
// 点多边形测试
vector<vector<Point> > contours;
findContours(src, contours, RETR_TREE, CHAIN_APPROX_SIMPLE);
Mat raw_dist(src.size(), CV_32F);
for (int i = 0; i < src.rows; i++)
{
for (int j = 0; j < src.cols; j++)
{
raw_dist.at<float>(i, j) = (float)pointPolygonTest(contours[0], Point2f((float)j, (float)i), true);
}
}
// 获取最大内接圆半径
double minVal, maxVal;
Point maxDistPt; // inscribed circle center
minMaxLoc(raw_dist, &minVal, &maxVal, NULL, &maxDistPt);
minVal = abs(minVal);
maxVal = abs(maxVal);
Mat drawing = Mat::zeros(src.size(), CV_8UC3);
for (int i = 0; i < src.rows; i++)
{
for (int j = 0; j < src.cols; j++)
{
if (raw_dist.at<float>(i, j) < 0)
{
drawing.at<Vec3b>(i, j)[0] = (uchar)(255 - abs(raw_dist.at<float>(i, j)) * 255 / minVal);
}
else if (raw_dist.at<float>(i, j) > 0)
{
drawing.at<Vec3b>(i, j)[2] = (uchar)(255 - raw_dist.at<float>(i, j) * 255 / maxVal);
}
else
{
drawing.at<Vec3b>(i, j)[0] = 255;
drawing.at<Vec3b>(i, j)[1] = 255;
drawing.at<Vec3b>(i, j)[2] = 255;
}
}
}
// draw max inner circle
circle(drawing, maxDistPt, (int)maxVal, Scalar(255, 255, 255));
imshow("Source", src);
imshow("Distance and inscribed circle", drawing);
waitKey();
return 0;
}
//5.凸包检测
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main(int argc, const char *argv[])
{
Mat src = imread("images/hand.jpg");
if (src.empty()) {
printf("could not load image...\n");
return -1;
}
namedWindow("input", WINDOW_AUTOSIZE);
imshow("input", src);
// 二值化
Mat dst, gray, binary;
cvtColor(src, gray, COLOR_BGR2GRAY);
threshold(gray, binary, 0, 255, THRESH_BINARY | THRESH_OTSU);
// 删除干扰块
Mat k = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));
morphologyEx(binary, binary, MORPH_OPEN, k);
imshow("binary", binary);
// 轮廓发现与绘制
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
findContours(binary, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE, Point());
for (size_t t = 0; t < contours.size(); t++) {
vector<Point> hull;
convexHull(contours[t], hull);
bool isHull = isContourConvex(contours[t]);
printf("test convex of the contours %s", isHull ? "Y" : "N");
int len = hull.size();
for (int i = 0; i < hull.size(); i++) {
circle(src, hull[i], 4, Scalar(255, 0, 0), 2, 8, 0);
line(src, hull[i%len], hull[(i + 1) % len], Scalar(0, 0, 255), 2, 8, 0);
}
}
imshow("contours", src);
waitKey(0);
return 0;
}
4.轮廓匹配
4.1 矩与Hu矩匹配
矩的定义:https://www.cnblogs.com/fcfc940503/p/11319251.html
// 计算几何矩与中心距
Moments cv::moments(
InputArray array,
bool binaryImage = false
)
//1.使用几何矩计算轮廓中心与横纵比过滤
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
findContours(binary, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE, Point());
for (size_t t = 0; t < contours.size(); t++) {
// 最小外接轮廓
RotatedRect rrt = minAreaRect(contours[t]);
float w = rrt.size.width;
float h = rrt.size.height;
Point2f pts[4];
rrt.points(pts);
// 计算横纵比
float ratio = min(w, h) / max(w, h);
Point2f cpt = rrt.center;
circle(src, cpt, 2, Scalar(255, 0, 0), 2, 8, 0);
if (ratio > 0.9) {
circle(src, cpt, 2, Scalar(255, 0, 0), 2, 8, 0);
// 绘制旋转矩形与中心位置
for (int i = 0; i < 4; i++) {
line(src, pts[i % 4], pts[(i + 1) % 4], Scalar(0, 0, 255), 2, 8, 0);
}
}
if (ratio < 0.5) {
circle(src, cpt, 2, Scalar(255, 0, 0), 2, 8, 0);
// 绘制旋转矩形与中心位置
for (int i = 0; i < 4; i++) {
line(src, pts[i % 4], pts[(i + 1) % 4], Scalar(0, 255, 0), 2, 8, 0);
}
}
}
// 使用Hu矩实现轮廓匹配
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
void contours_info(Mat &image, vector<vector<Point>> &pts);
int main(int argc, char** argv) {
Mat src = imread("images/abc.png");
imshow("input", src);
Mat src2 = imread("images/a5.png");
imshow("input2", src2);
// 轮廓提取
vector<vector<Point>> contours1;
vector<vector<Point>> contours2;
contours_info(src, contours1);
contours_info(src2, contours2);
// hu矩计算
Moments mm2 = moments(contours2[0]);
Mat hu2;
HuMoments(mm2, hu2);
// 轮廓匹配
for (size_t t = 0; t < contours1.size(); t++) {
Moments mm = moments(contours1[t]);
Mat hum;
HuMoments(mm, hum);
double dist = matchShapes(hum, hu2, CONTOURS_MATCH_I1, 0);
printf("contour match distance : %.2f\n", dist);
if (dist < 1) {
printf("draw it \n");
drawContours(src, contours1, t, Scalar(0, 0, 255), 2, 8);
}
}
imshow("match result", src);
waitKey(0);
return 0;
}
void contours_info(Mat &image, vector<vector<Point>> &contours) {
Mat gray, binary;
vector<Vec4i> hierarchy;
cvtColor(image, gray, COLOR_BGR2GRAY);
threshold(gray, binary, 0, 255, THRESH_BINARY | THRESH_OTSU);
findContours(binary, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
}
4.2 形状场景算法比较轮廓
4.2.1 形状场景距离提取
4.2.2 Hausdorff距离提取
参考
1.https://cloud.tencent.com/developer/article/1588277