提取轮廓的原理和代码实例

在检测物体的轮廓时,我们通常会使用到opencv中的findcontour和drawcontour,比较常用而且效果不错。那么findcontour是基于什么原理来实现轮廓的提取呢?

1985年,有个叫satoshi suzuki的人发表了一篇论文,Topological structural analysis of digitized binary images by border following,他介绍了两种算法来实现轮廓的提取,当然输入的图像是二值图像。findcontour就是基于这篇论文的思路来实现。

本博文分为三部分来写,第一部分是介绍satoshi suzuki论文的大致思想;第二部分是使用实例来阐述findcontour的运用;第三部分是代码的解释。

一、论文大概思想

这篇论文我看了一天多,内容还是比较好理解。他主要介绍了两种算法,用来对数字二值图像进行拓扑分析。第一种算法是在确定二值图像边界的围绕关系,即确定外边界、孔边界以及他们的层次关系,由于这些边界和原图的区域具有一一对应关系(外边界对应像素值为1的连通区域,孔边界对应像素值为0的区域),因此我们就可以用边界来表示原图。第二种算法,是第一种算法的修改版本,本质一样,但是它只找最外面的边界。

也许你会问,这个算法怎么来确定外边界,孔边界以及他们的层级关系?他采用编码的思想,给不同的边界赋予不同的整数值,从而我们就可以确定它是什么边界以及层次关系。输入的二值图像即为0和1的图像,用f(i,j)表示图像的像素值。每次行扫描,遇到以下两种情况终止:

(1)f(i,j-1)=0,f(i,j)=1;//f(i,j)是外边界的起始点

(2)f(i,j)>=1,f(i,j+1)=0;//f(i,j)是孔边界的起始点

然后从起始点开始,标记边界上的像素。在这里分配一个唯一的标示符给新发现的边界,叫做NBD。初始时NBD=1,每次发现一个新边界加1。在这个过程中,遇到f(p,q)=1,f(p,q+1)=0时,将f(p,q)置为-NBD。什么意思呢?就是右边边界的终止点。假如一个外边界里有孔边界时,怎么推导呢?限于篇幅,你可以看论文的附录1。

二、实例运用

下面举个例子,对一幅图像先进行边沿提取,这里使用canny,然后再用findcontour提取轮廓,最后用drawcontour画出轮廓。

#include "StdAfx.h"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include <iostream>
#include <stdio.h>
#include <stdlib.h>

using namespace cv;
using namespace std;

Mat src; Mat src_gray;
int thresh = 100;
int max_thresh = 255;
RNG rng(12345);

/// Function header
void thresh_callback(int, void* );

/** @function main */
int main()
{
    /// Load source image and convert it to gray
    src = imread("lena.jpg", 1 );

    /// Convert image to gray and blur it
    cvtColor( src, src_gray, CV_BGR2GRAY );
    blur( src_gray, src_gray, Size(3,3) );

    /// Create Window
    char* source_window = "Source";
    namedWindow( source_window, 0);
    imshow( source_window, src );

    createTrackbar( " Canny thresh:", "Source", &thresh, max_thresh, thresh_callback );
    thresh_callback( 0, 0 );

    waitKey(0);
    return(0);
}

/** @function thresh_callback */
void thresh_callback(int, void* )
{
    Mat canny_output;
    vector<vector<Point> > contours;
    vector<Vec4i> hierarchy;

    /// Detect edges using canny
    Canny( src_gray, canny_output, thresh, thresh*2, 3 );
    /// Find contours
    findContours( canny_output, contours, hierarchy, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE, Point(0, 0) );

    /// Draw contours
    Mat drawing = Mat::zeros( canny_output.size(), CV_8UC3 );
    for( int i = 0; i< contours.size(); i++ )
    {
        Scalar color = Scalar( rng.uniform(0, 255), rng.uniform(0,255), rng.uniform(0,255) );
        drawContours( drawing, contours, i, color, CV_FILLED, 8, hierarchy, 0, Point() );
    }

    /// Show in a window
    namedWindow( "Contours", 0 );
    imshow( "Contours", drawing );
}

结果图如下:

这里写图片描述
这里写图片描述

三、代码注释

findContours( canny_output, contours, hierarchy, 
CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE, Point(0, 0) );

canny_output:使用canny算子提取边缘的结果图,这里的图像必须是二值图像;
contours:提取得到的轮廓图,每个轮廓作为一个点向量来存储;

hierarchy:关于输出图像的拓扑信息,例如第i个轮廓,hierarchy[i][0]和hiearchy[i][1]表示该轮廓的前一个和后一个轮廓,同一层关系;hiearchy[i][2]和hiearchy[i][3]表示父轮廓和子轮廓,不同层的关系。

CV_RETR_EXTERNAL:轮廓检索模式,这里采用外轮廓的模式;

CV_CHAIN_APPROX_SIMPLE:轮廓的近似方法,这里采用压缩方式,只用端点来表示;

Point(0,0):偏移量,这里无偏移。

drawContours( drawing, contours, i, color,
CV_FILLED, 8, hierarchy, 0, Point() );

drawing:目标图像;
contours:所有输入的轮廓;

i:指定那个轮廓被画;

color:轮廓的颜色

CV_FILLED:线的宽度,这里采用填充模式;

8:线的连接度;

hierarchy:只有当你只需要画一些轮廓时,这个参数才需要:

0:所画轮廓的最大级别,为0时表示只画指定的轮廓;

Point():偏移量。

哈哈,终于好了!

  • 8
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值