Opencv之获取边缘和画轮廓
一、findContours()
- void findContours//提取轮廓,用于提取图像的轮廓
- (
- InputOutputArray image,//输入图像,必须是8位单通道图像,并且应该转化成二值的
- OutputArrayOfArrays contours,//检测到的轮廓,每个轮廓被表示成一个point向量
- OutputArray hierarchy,//可选的输出向量,包含图像的拓扑信息。其中元素的个数和检测到的轮廓的数量相等
- int mode,//说明需要的轮廓类型和希望的返回值方式
- int method,//轮廓近似方法
- Point offset = Point()
- )
CV_RETR_EXTERNAL 只检测出最外轮廓即c0。图2中第一个轮廓指向最外的序列,除此之外没有别的连接。
CV_RETR_LIST 检测出所有的轮廓并将他们保存到表(list)中,图2中描绘了这个表,被找到的9条轮廓相互之间由h_prev和h_next连接。这里并没有表达出纵向的连接关系,没有使用v_prev和v_next.
CV_RETR_COMP 检测出所有的轮廓并将他们组织成双层的结构,第一层是外部轮廓边界,第二层边界是孔的边界。从图2可以看到5个轮廓的边界,其中3个包含孔。最外层边界c0有两个孔,c0之间的所有孔相互间由h_prev和h_next指针连接。
CV_RETR_TREE 检测出所有轮廓并且重新建立网状的轮廓结构。图2中,根节点是最外层的边界c0,c0之下是孔h00,在同一层中与另一个孔h01相连接。同理,每个孔都有子节点(相对于c000和c010),这些子节点和父节点被垂直连接起来。这个步骤一直持续到图像最内层的轮廓,这些轮廓会成为树叶节点。
注意,method 也就是轮廓的近似方法
CV_CHAIN_CODE 用freeman链码输出轮廓,其他方法输出多边形(顶点的序列)。
CV_CHAIN_APPROX_NONE将链码编码中的所有点转换为点。
CV_CHAIN_APPROX_SIMPLE压缩水平,垂直或斜的部分,只保存最后一个点。
CV_CHAIN_APPROX_TC89_L1,CV_CHAIN_QPPROX_TC89_KCOS使用Teh-Chin链逼近算法中的一个。
CV_LINK_RUNS与上述的算法完全不同,连接所有的水平层次的轮廓。
二、drawContours()
- void drawContours//绘制轮廓,用于绘制找到的图像轮廓
- (
- InputOutputArray image,//要绘制轮廓的图像
- InputArrayOfArrays contours,//所有输入的轮廓,每个轮廓被保存成一个point向量
- int contourIdx,//指定要绘制轮廓的编号,如果是负数,则绘制所有的轮廓
- const Scalar& color,//绘制轮廓所用的颜色
- int thickness = 1, //绘制轮廓的线的粗细,如果是负数,则轮廓内部被填充
- int lineType = 8, /绘制轮廓的线的连通性
- InputArray hierarchy = noArray(),//关于层级的可选参数,只有绘制部分轮廓时才会用到
- int maxLevel = INT_MAX,//绘制轮廓的最高级别,这个参数只有hierarchy有效的时候才有效
- //maxLevel=0,绘制与输入轮廓属于同一等级的所有轮廓即输入轮廓和与其相邻的轮廓
- //maxLevel=1, 绘制与输入轮廓同一等级的所有轮廓与其子节点。
- //maxLevel=2,绘制与输入轮廓同一等级的所有轮廓与其子节点以及子节点的子节点
- Point offset = Point()
- )
三、程序实现
- #include <opencv2/opencv.hpp>
- #include<iostream>
- using namespace std;
- using namespace cv;
- #define WINDOWN_1 "原图"
- #define WINDOWN_2 "CANNY图"
- #define WINDOWN_3 "效果图"
- Mat srcImage, grayImage, out_Canny;
- int min_Thresh = 50;
- int max_Thresh = 250;
- vector<vector<Point>> g_vContours;
- vector<Vec4i> g_vHierarchy;
- RNG G_RNG(1234);
- void Find_Draw_COntours(int ,void*);
- int main()
- {
- srcImage = imread("D://vvoo//BMW_1.jpg");
- namedWindow(WINDOWN_1,1);
- imshow(WINDOWN_1, srcImage);
- cvtColor(srcImage, grayImage, COLOR_BGR2GRAY);
- blur(grayImage, grayImage, Size(3, 3));
- createTrackbar("CANNY 值:", WINDOWN_1, &min_Thresh, max_Thresh, Find_Draw_COntours);
- Find_Draw_COntours(0, 0);
- waitKey(0);
- return 0;
- }
- void Find_Draw_COntours(int, void*)
- {
- Canny(grayImage, out_Canny, min_Thresh, max_Thresh *2, 3);
- imshow(WINDOWN_2, out_Canny);
- findContours(out_Canny, g_vContours, g_vHierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));
- Mat Drawing = Mat::zeros(out_Canny.size(), CV_8UC3);
- for(int i= 0;i <g_vContours.size(); i++)
- {
- Scalar color = Scalar(G_RNG.uniform(0, 255), G_RNG.uniform(0, 255), G_RNG.uniform(0, 255));
- drawContours(Drawing, g_vContours, i, color, 2, 8, g_vHierarchy, 0, Point());
- }
- imshow(WINDOWN_3, Drawing);
- }
三、实际应用
提取到轮廓后,其实我们更关心的是如果把这些轮廓转换为可以利用的特征,也就是涉及到轮廓的描述问题,这时就有多种方法可以选择,比如矢量化为多边形、矩形、椭圆等。OpenCV里提供了一些这样的函数。
- // 轮廓表示为一个矩形
- Rect r = boundingRect(Mat(contours[0]));
- rectangle(result, r, Scalar(255), 2);
- // 轮廓表示为一个圆
- float radius;
- Point2f center;
- minEnclosingCircle(Mat(contours[1]), center, radius);
- circle(result, Point(center), static_cast<int>(radius), Scalar(255), 2);
- // 轮廓表示为一个多边形
- vector<Point> poly;
- approxPolyDP(Mat(contours[2]), poly, 5, true);
- vector<Point>::const_iterator itp = poly.begin();
- while (itp != (poly.end() - 1))
- {
- line(result, *itp, *(itp + 1), Scalar(255), 2);
- ++itp;
- }
- line(result, *itp, *(poly.begin()), Scalar(255), 2);
- // 轮廓表示为凸多边形
- vector<Point> hull;
- convexHull(Mat(contours[3]), hull);
- vector<Point>::const_iterator ith = hull.begin();
- while (ith != (hull.end() - 1))
- {
- line(result, *ith, *(ith + 1), Scalar(255), 2);
- ++ith;
- }
- line(result, *ith, *(hull.begin()), Scalar(255), 2);
程序中我们依次画了矩形、圆、多边形和凸多边形。最终效果如下:
- #include<opencv2\opencv.hpp>
- #include<iostream>
- using namespace cv;
- using namespace std;
- #define WINDOW_1 "原图"
- #define WINDOW_2 "效果图"
- int min_area = 13;
- int max_area = 100;
- vector<vector<Point>> g_vContours;
- vector<Vec4i> g_vHierarchy;
- RNG g_rng(12345);
- Mat srcImage,grayImage,b_grayImage,binaryImage;
- Mat foreground, d_foreground, c_foreground;
- void on_ThreshChange(int, void*);
- int main()
- {
- srcImage=imread("D://vvoo//soldier.jpg",1);
- cvtColor(srcImage, grayImage, COLOR_BGR2GRAY);
- blur(grayImage, b_grayImage, Size(3, 3));
- threshold(b_grayImage, binaryImage, 100, 255, CV_THRESH_BINARY);
- Mat element_1 = getStructuringElement(MORPH_RECT, Size(3,3));
- Mat element_2 = getStructuringElement(MORPH_RECT, Size(5,5));
- erode(binaryImage, foreground, element_1);
- dilate(foreground, d_foreground, element_2);
- namedWindow(WINDOW_1, WINDOW_AUTOSIZE);
- /*imshow(WINDOW_1, srcImage);*/
- createTrackbar("area 值:", WINDOW_1, &min_area, max_area, on_ThreshChange);
- on_ThreshChange(0, 0);
- /* imshow("二值化", binaryImage);
- imshow("形态学处理后前景", d_foreground);*/
- /*imshow("Canny", c_foreground);*/
- waitKey(0);
- return 0;
- }
- void on_ThreshChange(int, void*)
- {
- Canny(d_foreground, c_foreground, 100, 200,3);
- findContours(c_foreground, g_vContours, g_vHierarchy, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE, Point(0, 0));
- Mat drawing = Mat::zeros(srcImage.size(), CV_8UC3);
- for (int i = 0; i< g_vContours.size(); i++)
- {
- drawContours(drawing, g_vContours, i, Scalar(255,255,255), 3, 18, g_vHierarchy, 0, Point());
- double area = contourArea(Mat(g_vContours[i]));//计算轮廓面积
- if (area>min_area && area < 100)
- {
- Scalar color = Scalar(g_rng.uniform(0, 255), g_rng.uniform(0, 255), g_rng.uniform(0, 255));//任意值
- Rect rect = boundingRect(Mat(g_vContours[i]));//计算点集的最外面矩形边界
- rectangle(drawing, rect,color, 2);
- rectangle(srcImage, rect, /*Scalar(0,0,255)*/color, 2);
- rectangle(c_foreground, rect, color, 2);
- }
- }
- imshow(WINDOW_1, srcImage);
- imshow(WINDOW_2, drawing);
- imshow("Canny", c_foreground);
- }
不明白哪里错了,为什么在原图srcImage上画的矩形基本上不随着滑动按钮而变化呢?求解!
按理说三幅图的矩形框应该是一样的,但最后一张和前两张不一样,why?