1. findContours 找轮廓
void findContours( InputOutputArray image, OutputArrayOfArrays contours,
OutputArray hierarchy, int mode,
int method, Point offset = Point());
InputOutputArray image
:输入图像是8位单通道的图像(256级灰度图)。其中像素点的非0灰度值被当成1(转化后即为255),0值保持0,所以输入图像被当成一个二值图像对待。
可以用
compare() , inRange() , threshold() , adaptiveThreshold() , Canny()
或者其他方法来从灰度图或者彩色图中生成二值图像。该函数在提取轮廓的过程中会改变图像。如果第4个参数 mode 为 CV_RETR_CCOMP 或者
CV_RETR_FLOODFILL,输入图像也可以是32位的整型图像(CV_32SC1)。OutputArrayOfArrays contours
: 检测到的轮廓Each contour is stored as a vector of points. 每个轮廓会被存储为
vector
所以 contours 的类型是
vector
。> OutputArray hierarchy
: 可选的输出向量,包含图像的拓扑信息It has as many elements as the number of contours. 元素个数 = 轮廓数
对于第 i 个轮廓
contours[i]
,hierarchy 的以下元素分别表示
hierarchy[i][0]: the next contour at the same hierarchical level
hierarchy[i][1]: the previous contour at the same hierarchical level
hierarchy[i][2]: the first child contour
hierarchy[i][3]: the parent contour
hierarchy 的这些元素的原始值为0,如果不存在,置为负数
int mode
: Contour retrieval mode 取回轮廓模式(复杂度依次增加)CV_RETR_EXTERNAL retrieves only the extreme outer contours.
It setshierarchy[i][2]=hierarchy[i][3]=-1
for all the contours.
只取回最外侧轮廓CV_RETR_LIST retrieves all of the contours without establishing any hierarchical relationships.
取回所有轮廓,但是不建立层次关系CV_RETR_CCOMP 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.
取回所有轮廓并组织成为两层
顶层是外部边界,第二层是洞的边界
如果有另外一个轮廓在一个连通部分的洞中,放到顶层CV_RETR_TREE retrieves all of the contours and reconstructs a full hierarchy of nested contours.
取回所有轮廓并生成完整的嵌套的轮廓层次结构int method
: Contour approximation method 轮廓估计方法
- CV_CHAIN_APPROX_NONE 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
.
存储轮廓内所有相邻(水平、垂直、对角)的点
CV_CHAIN_APPROX_SIMPLE compresses horizontal, vertical, and diagonal segments and leaves only their end points. For example, an up-right rectangular contour is encoded with 4 points.
压缩轮廓内(水平、垂直、对角)的点,只存储边界点,比如矩形就只存储4个点。CV_CHAIN_APPROX_TC89_L1,CV_CHAIN_APPROX_TC89_KCOS applies one of the flavors of the Teh-Chin chain approximation algorithm. See [TehChin89] for details.
Point offset = Point()
可选的轮廓偏移
- This is useful if the contours are extracted from the image ROI and then they should be analyzed in the whole image context. 从 ROI 转移到 全局图片
2. drawContours 画轮廓
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() );
-
InputOutputArray image
Destination image. -
InputArrayOfArrays contours
All the input contours. Each contour is stored as a point vector(顶点向量) -
int contourIdx
Parameter indicating a contour to draw. If it is negative, all the contours are drawn. 如果为负,所有的轮廓已经画出 -
const Scalar& color
Color of the contours. 轮廓颜色 -
int thickness = 1
Thickness of lines the contours are drawn with. 轮廓宽度
If it is negative (for example,thickness=CV_FILLED
), the contour interiors are drawn. -
int lineType = LINE_8
Line connectivity. See line() for details. -
InputArray hierarchy = noArray()
Optional information about hierarchy. It is only needed if you want to draw only some of the contours (see maxLevel ). -
int maxLevel = INT_MAX
Maximal level for drawn contours. If it is 0, only the specified contour is drawn. If it is 1, the function draws the contour(s) and all the nested contours. If it is 2, the function draws the contours, all the nested contours, all the nested-to-nested contours, and so on. This parameter is only taken into account when there is hierarchy available. -
Point offset = Point()
Optional contour shift parameter. Shift all the drawn contours by the specifiedoffset=(dx,dy)
.
3. 实例代码
核心部分是回调函数
void on_trackbar(int, void *)
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include
#include
using namespace cv;
using namespace std;
Mat img;
int threshval = 160; // 轨迹条滑块对应的值,给初值160,命名这里用threshold会ambiguous
// 扫描灰度图像矩阵
void scanImageMatrixG(Mat &img) {
for (int i = 0; i < 10; ++i) {
for (int j = 0; j < 10; ++j) {
cout << setw(3) << (int) img.at(i, j) << ", ";
}
cout << endl;
}
cout << endl << endl;
}
void on_trackbar(int, void *) {
// 通过 threshval 二值化图像
// threshval = 160 > 128, 选择 img > threshval
// img 像素值 >160, 取 255,<=160,取 0
Mat threshImage = threshval < 128 ? (img < threshval) : (img > threshval);
cout << getTrackbarPos("threshold", "trackbar") << endl;
scanImageMatrixG(threshImage);
// 输出的点的位置
vector> contours; // 二维的点 { {[1,1], [1,2]}, ... }
// 输出图像有4个通道
vector hierarchy; // vector> { { [1,2,3,4], [1,1,1,1], ... }, ... }
// 寻找轮廓
// CV_RETR_CCOMP: 取回轮廓采用两层结构
// CV_CHAIN_APPROX_SIMPLE: 轮廓估计采用压缩轮廓
findContours(threshImage, contours, hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE);
// 必须用zeros初始化dst
// 回调函数每次调用目标矩阵dst都必须是个空矩阵
// 不然会在上一个矩阵的基础上叠加值
Mat dst = Mat::zeros(img.size(), CV_8UC3); // 类方法
// 轮廓上色
if (!contours.empty() && !hierarchy.empty()) {
// 遍历所有顶层轮廓,顶层体现在 hierarchy[idx][0],同层
// 随机生成颜色值绘制给各连接组成部分
int idx = 0;
for (; idx >= 0; idx = hierarchy[idx][0]) { // hierarchy[idx][0]: the next contour at the same hierarchical level
Scalar color((rand() & 255), (rand() & 255), (rand() & 255)); // 随机色
drawContours(dst, contours, idx, color, CV_FILLED, 8, hierarchy); // 绘制填充轮廓
}
}
// 显示轮廓检测后的图像
imshow("trackbar", dst);
}
int main() {
// 读入灰度图
img = imread("../pictures/pig.jpg", 0);
// 显示原图
namedWindow("img");
imshow("img", img);
// 原图灰度矩阵
scanImageMatrixG(img);
// 创建处理窗口
namedWindow("trackbar");
// 创建轨迹条
createTrackbar("threshold", "trackbar", &threshval, 255, on_trackbar);
// 轨迹条回调函数
on_trackbar(threshval, 0); // threshval: 轨迹条位置,是全局变量,userdata = 0
waitKey(0);
return 0;
}
(1)threshold = 160
原图灰度矩阵 & 二值矩阵 threshImage
64, 65, 66, 68, 68, 68, 68, 67, 58, 63,
63, 64, 66, 67, 68, 68, 67, 67, 58, 62,
65, 67, 68, 70, 71, 71, 70, 70, 65, 68,
75, 76, 78, 80, 81, 81, 81, 80, 82, 85,
94, 96, 97, 99, 101, 101, 101, 100, 103, 105,
120, 121, 123, 125, 127, 127, 127, 127, 124, 126,
145, 146, 148, 151, 152, 153, 153, 153, 145, 147,
160, 162, 164, 166, 168, 168, 168, 168, 162, 163,
170, 171, 173, 175, 176, 176, 176, 175, 175, 175,
174, 176, 178, 180, 182, 183, 183, 183, 181, 181,
160
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
(2)threshold = 45
二值矩阵 threshImage
45
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
因为判定方法为
Mat threshImage = threshval < 128 ? (img < threshval) : (img > threshval);
45 < 128, 选择 img < threshval
,而像素值全部 >45,所以全为 0
(3)threshold = 200
二值矩阵 threshImage
200
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
因为判定方法为
Mat threshImage = threshval < 128 ? (img < threshval) : (img > threshval);
200 > 128, 选择 img > threshval
,而像素值全部 <200,所以全为 0
4. 总结
轮廓检测的步骤:
-
imread("pic", 0)
读入灰度图
img = imread("../pictures/pig.jpg", 0);
-
threshold
将灰度图分为二值图 (阈值影响着轮廓检测精确度)
Mat threshImage = threshval < 128 ? (img < threshval) : (img > threshval);
-
findContours
寻找图像轮廓
// 寻找轮廓
// CV_RETR_CCOMP: 取回轮廓采用两层结构
// CV_CHAIN_APPROX_SIMPLE: 轮廓估计采用压缩轮廓
findContours(threshImage, contours, hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE);
-
drawContours
画出找到的轮廓
// 轮廓上色
if (!contours.empty() && !hierarchy.empty()) {
// 遍历所有顶层轮廓,顶层体现在 hierarchy[idx][0],同层
// 随机生成颜色值绘制给各连接组成部分
int idx = 0;
for (; idx >= 0; idx = hierarchy[idx][0]) { // hierarchy[idx][0]: the next contour at the same hierarchical level
Scalar color((rand() & 255), (rand() & 255), (rand() & 255)); // 随机色
drawContours(dst, contours, idx, color, CV_FILLED, 8, hierarchy); // 绘制填充轮廓
}
}
轮廓上色用随机色的好处:一种颜色就代表了一个轮廓
如果用单一的颜色,比如白色
Scalar color(255, 255, 255); // 白色
那么所有的轮廓就不能区分开了