【译】Learn OpenCV之Convex Hull

这篇文章讲的是如何寻找给出的点集的凸包(Convex Hull),先简单介绍算法原理,之后利用OpenCV实现一个找凸包的程序。

什么是凸包(Convex Hull)?

这个问题可以分成两个概念理解,Convex 和 Hull

凸形状(Convex object)就是没有大于180°的内角的形状。不是凸形状的称为非凸(Non-Convex)或者凹的(Concave)。下图就是凸形状图像和凹形状图像的例子。


Convex-Concave.png

包(Hull)可以理解为一个物体的外形。

因此,凸包就是围绕点或形状的紧密拟合的凸边界。

上图中两个图像的凸包如下图所示。一个凸形状的凸包就是其边界,而一个凹边形的凸包就是一个能紧密包围该凹边形的凸边界。


convex-hull.jpg

Gift Wrapping Algorithms

给定一个点集,如何找出该点集的凸包?找凸包的算法称为Gift Wrapping Algorithms。有个YOUTUbe视频(打不开的话进原文观看)通过动画形式讲述了几个寻找凸包的算法。

表面看起来简单的算法,如果考虑上一些约束的话,事情就会变得不那么简单了。比如考虑算法的时间复杂度。

Jarvis March算法来说,时间复杂度为O(nh),其中n是输入的点集总数,h是凸包上的点的个数。另外一个算法Chan's algorithm复杂度为O(nlogh)。

是否有复杂度为O(n)的算法?答案是有的。但是在这些算法的发展历史中有些尴尬的事情。

第一个O(n)算法是Sklansky在1972年发表。之后被证明是错的。在1972-1989几年中,有16个O(n)算法发表,但是有7个被证明是错的。更令人尴尬的是OpenCV实现的寻找凸包算法(Sklansky (1982))。也被证明是错的。

但是不要方。OpenCV实现的这个算法仍是一个很流行的算法而且在绝大多数情况下输出是正确的。下面来看看如何使用OpenCV来寻找凸包。

利用OpenCV寻找凸包

通过上面描述我们知道Gift Wrapping algorithms的目的是寻找点集的凸包。

那么我们如何将这个算法在图像中使用呢?

方法如下:先将一张图片二值化,然后找到图像中的轮廓,最后找出各个轮廓的凸包。下面一步一步通过代码来讲解:

Step 1:读入图片

Python

src = cv2.imread("sample.jpg", 1) # read input image

C++

Mat src;
src = imread("sample.jpg", 1); // read input image
Step 2: 二值化图像

二值化图像有下面三步:

  1. 讲图片转化为灰度图像
  2. 通过blur函数去除一些噪点
  3. 将灰度图像二值化

代码如下:
Python

gray = cv2.cvtColor(src, cv2.COLOR_BGR2GRAY) # convert to grayscale
blur = cv2.blur(gray, (3, 3)) # blur the image
ret, thresh = cv2.threshold(blur, 50, 255, cv2.THRESH_BINARY)

C++

Mat gray, blur_image, threshold_output;
cvtColor(src, gray, COLOR_BGR2GRAY); //convert to grayscale
blur(gray, blur_image, Size(3, 3)); // apply blur to grayscaled image
threshold(blur_image, threshold_output, 50, 255, THRESH_BINARY); //apply binary thresholding

三步的输出分别如下图所示


preprocessing-operations-1.png
Step 3: 使用findContour寻找轮廓

下面我们使用OpenCV的findContour函数找到二值图像中的所有轮廓。你可能会问为什么不适用边缘检测,边缘检测出的只是每个边缘的位置,而findContour函数返回的是每个轮廓为集合的一个列表,这是我们需要的。
Python

# Finding contours for the threshold image
im2, contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

C++

vector<vector<Point> > contours; // list of contours points
vector<Vec4i> hierarchy;
// find contours
findContours(threshold_output, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0))

结果如下


contours.png
Step 4:使用convexHull函数找轮廓的凸包

找凸包的函数使用方法如下

Python

# create hull array for convex hull points
hull = []

#calculate points for each contour
for i in range(len(contours)):
    #creating convex hull object for each contour
    hull.append(cv2.convexHull(contours[i], False))

C++

// create hull array for convex hull points
vector<vector<Point> > hull(contours.size());
for(int i = 0; i < contours.size(); i++)
    convexHull(Mat(contours[i]), hull[i], False);
Step 5:显示凸包

因为凸包也是一种轮廓,所以可以使用OpenCV中的drawContours函数画出来。

Python

# create an empty black image
drawing = np.zeros((thresh.shape[0], thresh.shape[1], 3), np.uint8)

# draw contours and hull points
for i in range(len(contours)):
    color_contours = (0, 255, 0) # green - color for contours
    color = (255, 0, 0) # blue - color for convex hull
    # draw ith contour
    cv2.drawContours(drawing, contours, i, color_contours, 1, 8, hierarchy)
    # draw ith convex hull object
    cv2.drawContours(drawing, hull, i, color, 1, 8)

C++

// create a blank image (black image)
Mat drawing = Mat::zeros(threhold_output.size(), CV_8UC3)

for(int i = 0; i < contours.size(); i++){
    Scalar color_contours = Scalar(0, 255, 0); // green - color for contours
    Scalar color = Scalar(255, 0, 0); // blue - color for convex hull
    // draw ith contour
    drawContours(drawing, contours, i, colot_contours, 1, 8, vector<Vec4i>(), 0, Point());
    // draw ith contour
    drawContours(drawing, hull, i, color, 1, 8, vector<Vec4i>(), 0, Point());
}

最终输入如下图所示


output-convex-hull.png

关于凸包的应用

下面举几个凸包应用的例子

给一个点集找边界

比如之前的换脸的应用。通过Dlib库检测出人脸特征点后需要提取出人脸,这时候可以用寻找凸包的方法,如下图所示

convex-hull-example.jpg

这方面还有其他应用,比如Kinect,灰度深度图像就是一个点集,我们需要利用凸包来定位物体在场景中的位置等等。

防止发生碰撞(Collision Avoidance)

想象一辆汽车由一个点集构成的,如下图所示,如果想防止这辆汽车与其他障碍物发生碰撞,求任意轮廓之间的交比求两个凸多边形之间的冲突在计算量上要大多,这时候更应该使用凸包来代替轮廓。


convex-hull-car.png

参考文献
1.Applications and Algorithms of Convex Hull : Brilliant
2.Series of Convex Hull Algorithms and their Implementations: GeeksForGeeks
3.ConvexHull Documentation: OpenCV Docs
4.Sklansky’s Algorithm (1982)

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

推荐阅读更多精彩内容