
3.9.1 边框、最小矩形区域以及最小外接圆
找出正方形的轮廓非常简单,不规则、倾斜和旋转的形状则需要充分发挥OpenCV的cv2.findContours函数的潜力。我们来看如图3-5所示的图像。

图3-5 示例图像
在实际应用中,我们最感兴趣的是确定主体的边框、最小外接矩形及其外接圆。cv2.findContours函数结合一些其他OpenCV实用程序,使这一任务非常容易实现。首先,下面的代码从文件读取一幅图像并将其转换为灰度图像,对灰度图像应用阈值,并在阈值化图像中找到轮廓:


其次,针对每个轮廓寻找并画出边框、最小外接矩形和最小外接圆,如下列代码所示:

最后,使用下列代码绘制轮廓并在窗口中显示图像,直到用户按下某个键:

请注意,轮廓检测是在阈值化图像上进行的,因此在这一阶段颜色信息已经丢失了,但是我们是在原始彩色图像上绘制,所以显示的是彩色结果。
我们回过头来更仔细地看一下之前的for循环中执行的步骤——在for循环中处理每个检测到的轮廓。首先,计算一个简单的边框:

这是一个非常简单的方法,可以把轮廓信息转换为矩形的(x,y)坐标、高度和宽度。绘制矩形非常简单,可以用下面的代码实现:

接下来,计算包围主体的最小矩形区域:


这里使用的机制特别有趣:OpenCV没有可以直接从轮廓信息计算最小矩形顶点坐标的函数。相反,我们先计算最小矩形区域,然后计算矩形的顶点。请注意,计算的顶点是用浮点数表示的,而像素是用整数访问的(就OpenCV的绘图函数而言,不能访问半个像素),因此我们需要进行变换。接下来,画一个框,这样才有机会引入cv2.drawContours函数:

这个函数就像所有的OpenCV绘图函数一样,都会修改原始图像。请注意,它的第2个参数接受一个轮廓线数组,这样就可以在一个操作中绘制多条轮廓线。因此,如果一组点代表一个多边形的轮廓,那么需要将这些点封装在一个数组中,就像前面的示例中对边框所做的那样。第3个参数指定要绘制的contours数组的索引:值为-1,则绘制所有的轮廓线;否则,就绘制contours数组(第2个参数)中指定的索引处的轮廓线。
大多数绘图函数将绘制的颜色(表示为BGR元组)及线宽(以像素为单位)作为最后2个参数。
我们要研究的最后一个边界轮廓是最小外接圆:

cv2.minEnclosingCircle函数的唯一特点是它返回一个二元组,其中第一个元素本身就是一个元组,表示圆心的坐标,第二个元素是圆的半径。在将所有的值转换为整数之后,画圆就非常简单了。
将前面的代码应用于原始图像,最终的结果如图3-6所示。

图3-6 生成的最小外接圆和最小外接矩形
这是一个很好的结果,因为圆和矩形紧紧包围着物体。显然,这个物体不是圆形或者矩形的,所以我们可以用更适合的其他形状。接下来就来完成这个任务吧。