1. findContours 找輪廓
void findContours( InputOutputArray image, OutputArrayOfArrays contours,
OutputArray hierarchy, int mode,
int method, Point offset = Point());
InputOutputArray image
:輸入圖像是8位單通道的圖像(256級(jí)灰度圖)漂彤。其中像素點(diǎn)的非0灰度值被當(dāng)成1(轉(zhuǎn)化后即為255)榄攀,0值保持0,所以輸入圖像被當(dāng)成一個(gè)二值圖像對(duì)待。
可以用
compare() , inRange() , threshold() , adaptiveThreshold() , Canny()
或者其他方法來從灰度圖或者彩色圖中生成二值圖像。該函數(shù)在提取輪廓的過程中會(huì)改變圖像。如果第4個(gè)參數(shù) mode 為 CV_RETR_CCOMP 或者
CV_RETR_FLOODFILL淤井,輸入圖像也可以是32位的整型圖像(CV_32SC1)。OutputArrayOfArrays contours
: 檢測(cè)到的輪廓Each contour is stored as a vector of points. 每個(gè)輪廓會(huì)被存儲(chǔ)為
vector<Point>
所以 contours 的類型是
vector<vector<Point>>
摊趾。OutputArray hierarchy
: 可選的輸出向量币狠,包含圖像的拓?fù)湫畔?/p>It has as many elements as the number of contours. 元素個(gè)數(shù) = 輪廓數(shù)
對(duì)于第 i 個(gè)輪廓
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砾层,如果不存在漩绵,置為負(fù)數(shù)
int mode
: Contour retrieval mode 取回輪廓模式(復(fù)雜度依次增加)CV_RETR_EXTERNAL retrieves only the extreme outer contours.
It setshierarchy[i][2]=hierarchy[i][3]=-1
for all the contours.
只取回最外側(cè)輪廓CV_RETR_LIST retrieves all of the contours without establishing any hierarchical relationships.
取回所有輪廓,但是不建立層次關(guān)系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.
取回所有輪廓并組織成為兩層
頂層是外部邊界肛炮,第二層是洞的邊界
如果有另外一個(gè)輪廓在一個(gè)連通部分的洞中止吐,放到頂層CV_RETR_TREE retrieves all of the contours and reconstructs a full hierarchy of nested contours.
取回所有輪廓并生成完整的嵌套的輪廓層次結(jié)構(gòu)int method
: Contour approximation method 輪廓估計(jì)方法
- 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
.
存儲(chǔ)輪廓內(nèi)所有相鄰(水平宝踪、垂直、對(duì)角)的點(diǎn)
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.
壓縮輪廓內(nèi)(水平碍扔、垂直瘩燥、對(duì)角)的點(diǎn),只存儲(chǔ)邊界點(diǎn)不同,比如矩形就只存儲(chǔ)4個(gè)點(diǎn)颤芬。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 轉(zhuǎn)移到 全局圖片
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(頂點(diǎn)向量) -
int contourIdx
Parameter indicating a contour to draw. If it is negative, all the contours are drawn. 如果為負(fù),所有的輪廓已經(jīng)畫出 -
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. 實(shí)例代碼
核心部分是回調(diào)函數(shù)
void on_trackbar(int, void *)
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <iostream>
#include <iomanip>
using namespace cv;
using namespace std;
Mat img;
int threshval = 160; // 軌跡條滑塊對(duì)應(yīng)的值套鹅,給初值160,命名這里用threshold會(huì)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<uchar>(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);
// 輸出的點(diǎn)的位置
vector<vector<Point>> contours; // 二維的點(diǎn) { {[1,1], [1,2]}, ... }
// 輸出圖像有4個(gè)通道
vector<Vec4i> hierarchy; // vector<Vec<int, 4>> { { [1,2,3,4], [1,1,1,1], ... }, ... }
// 尋找輪廓
// CV_RETR_CCOMP: 取回輪廓采用兩層結(jié)構(gòu)
// CV_CHAIN_APPROX_SIMPLE: 輪廓估計(jì)采用壓縮輪廓
findContours(threshImage, contours, hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE);
// 必須用zeros初始化dst
// 回調(diào)函數(shù)每次調(diào)用目標(biāo)矩陣dst都必須是個(gè)空矩陣
// 不然會(huì)在上一個(gè)矩陣的基礎(chǔ)上疊加值
Mat dst = Mat::zeros(img.size(), CV_8UC3); // 類方法
// 輪廓上色
if (!contours.empty() && !hierarchy.empty()) {
// 遍歷所有頂層輪廓,頂層體現(xiàn)在 hierarchy[idx][0]留荔,同層
// 隨機(jī)生成顏色值繪制給各連接組成部分
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)); // 隨機(jī)色
drawContours(dst, contours, idx, color, CV_FILLED, 8, hierarchy); // 繪制填充輪廓
}
}
// 顯示輪廓檢測(cè)后的圖像
imshow("trackbar", dst);
}
int main() {
// 讀入灰度圖
img = imread("../pictures/pig.jpg", 0);
// 顯示原圖
namedWindow("img");
imshow("img", img);
// 原圖灰度矩陣
scanImageMatrixG(img);
// 創(chuàng)建處理窗口
namedWindow("trackbar");
// 創(chuàng)建軌跡條
createTrackbar("threshold", "trackbar", &threshval, 255, on_trackbar);
// 軌跡條回調(diào)函數(shù)
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,
因?yàn)榕卸ǚ椒?/p>
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,
因?yàn)榕卸ǚ椒?/p>
Mat threshImage = threshval < 128 ? (img < threshval) : (img > threshval);
200 > 128, 選擇 img > threshval
,而像素值全部 <200碘勉,所以全為 0
4. 總結(jié)
輪廓檢測(cè)的步驟:
-
imread("pic", 0)
讀入灰度圖
img = imread("../pictures/pig.jpg", 0);
-
threshold
將灰度圖分為二值圖 (閾值影響著輪廓檢測(cè)精確度)
Mat threshImage = threshval < 128 ? (img < threshval) : (img > threshval);
-
findContours
尋找圖像輪廓
// 尋找輪廓
// CV_RETR_CCOMP: 取回輪廓采用兩層結(jié)構(gòu)
// CV_CHAIN_APPROX_SIMPLE: 輪廓估計(jì)采用壓縮輪廓
findContours(threshImage, contours, hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE);
-
drawContours
畫出找到的輪廓
// 輪廓上色
if (!contours.empty() && !hierarchy.empty()) {
// 遍歷所有頂層輪廓巷挥,頂層體現(xiàn)在 hierarchy[idx][0],同層
// 隨機(jī)生成顏色值繪制給各連接組成部分
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)); // 隨機(jī)色
drawContours(dst, contours, idx, color, CV_FILLED, 8, hierarchy); // 繪制填充輪廓
}
}
輪廓上色用隨機(jī)色的好處:一種顏色就代表了一個(gè)輪廓
如果用單一的顏色验靡,比如白色
Scalar color(255, 255, 255); // 白色
那么所有的輪廓就不能區(qū)分開了