findContours 轮廓检测

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<Point>

  • 所以 contours 的类型是vector<vector<Point>>

  • 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 sets hierarchy[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 specified offset=(dx,dy).

3. 实例代码

核心部分是回调函数

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; // 轨迹条滑块对应的值,给初值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<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);

    // 输出的点的位置
    vector<vector<Point>> contours; // 二维的点 { {[1,1], [1,2]}, ... }

    // 输出图像有4个通道
    vector<Vec4i> hierarchy; // vector<Vec<int, 4>> { { [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. 总结

轮廓检测的步骤:

  1. imread("pic", 0) 读入灰度图
img = imread("../pictures/pig.jpg", 0);
  1. threshold 将灰度图分为二值图 (阈值影响着轮廓检测精确度)
Mat threshImage = threshval < 128 ? (img < threshval) : (img > threshval);
  1. findContours 寻找图像轮廓
// 寻找轮廓
// CV_RETR_CCOMP: 取回轮廓采用两层结构
// CV_CHAIN_APPROX_SIMPLE: 轮廓估计采用压缩轮廓
findContours(threshImage, contours, hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE);
  1. 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); // 白色

那么所有的轮廓就不能区分开了

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,458评论 6 513
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,030评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,879评论 0 358
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,278评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,296评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,019评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,633评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,541评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,068评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,181评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,318评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,991评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,670评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,183评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,302评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,655评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,327评论 2 358

推荐阅读更多精彩内容

  • Opencv 轮廓检测相关api文档 opencv2的c++接口 官方文档相关api Finds contours...
    东林钟声阅读 41,277评论 2 16
  • 我们今天要讲一个关于狐狸的故事。 森林里的小动物总是惧怕老虎,不屑于狐狸的狡猾,狐狸因此非常郁闷。在他看来,老虎并...
    纸上人生阅读 1,029评论 0 4
  • 2点半阅读 152评论 0 0
  • 时至今日的阅读已不再墨守着纸介,传统书籍、微博微信、QQ日志、电子书,丰盛而轻盈的方式提供了无限便捷的阅读天地,当...
    舒天阅读 642评论 0 1