3.3 使用OpenCV“抠图”——基于颜色通道以及形态特征

3.2节用简单的矩阵操作方法对二维码图片进行了一系列操作,包括去Logo、降低像素等。本节谈一谈如何对图片进行一些比较复杂的操作,比如我们3.1.1节中用数组切片来抓取足球,使用的命令是:

    ball = img[280:340, 330:390]

为什么是这个坐标?这个坐标是我们人眼看出来的,能否让计算机自动识别这个足球的位置?这里提供一种基于OpenCV的“套路”。

首先,还是需要认真观察数据,就是刚才抓下来的球(如图3-11所示):

图3-11 从原图中截取足球区域

    plt.imshow(ball)

这个球具有以下特点:

  • 看起来是个圆形。
  • 颜色是黄色+藏蓝色。
  • 由于在草地上,因此背景是绿色。

根据这三个特点,我们大致确定思路,几个步骤依次对应下面几个特点:

  • 看看有没有圆形的识别算法——Hough变换。
  • 用黄色+藏蓝色将球从背景提取出来。
  • 用绿色过滤背景色。

我们观察这张球小型图片中的60×60 =3600个像素,其中RGB的分布如何。这里仿照第2章用过的seaborn的pairplot函数来表达RGB三种颜色的两两组合,其结果如图3-12所示:

图3-12 图3-11中各像素点RGB的分布情况

图中的黄色与蓝黑色均为足球的颜色,而绿色则是足球场的颜色。我们接下来要做的就是找一个规则来区分足球与足球场。这里简单地写一个规则组合,其结果如图3-13所示:

    fig = plt.figure(figsize=(15, 5))
    ax1 = fig.add_subplot(131)
    ax2 = fig.add_subplot(132)
    ax3 = fig.add_subplot(133)
    ax1.imshow( (ball[:,:,0]>200) )
    ax2.imshow( (ball[:,:,0]>130)+(ball[:,:,0]<50) )
    ax3.imshow( (ball[:,:,0]>130)+(ball[:,:,0]<50)+(ball[:,:,1]<120) )

图3-13 使用颜色规则对球的区域进行二值化(黑色代表球的区域)

好了,根据颜色规则,我们已经依稀得到了一个圆形物体。下一步,将这一规则推广到整张图片中,其结果如图3-14所示。

    fig = plt.figure(figsize=(12, 5))
    ax1 = fig.add_subplot(121)
    ax2 = fig.add_subplot(122)
img1 = ((img[:,:,0]>130)+(img[:,:,0]<50)+(img[:,:,1]<120)).astype(np.uint8) ax1.imshow(img) ax2.imshow(img1)

图3-14 使用颜色规则对整张图片球的区域进行二值化(黑色代表球的区域)

由此可见,对整张图片进行的颜色二值化处理还是比较有效的,至少球这里出现了圆形的形状,我们经过简单的处理后,将这里的圆形提取出来即可。但是这里任务并不轻松,因为图中还是比较杂乱:首先,球中间的黑色部分含有白色,需要填充掉;其次,背景部分有很多观众、标语造成的白色环状噪点,这些噪点不大,但会在圆形检测环节干扰结果。

为了让图片结果减少噪点,我们这里利用OpenCV进行一组侵蚀(erode)稀释(dilute)操作,如图3-15(对图3-14的结果先进行2像素侵蚀,再进行8像素的稀释,最后进行3像素的侵蚀的整个过程)所示。形象地说,可以将图中的黑色区域看成是海岛,白色看成是海洋。进行侵蚀操作后,海水上涨,白色区域变多,将孤立的黑色区域“淹没”;然后进行稀释操作。此时海水退去,没有被完全淹没的、面积较大的海岛基本恢复以前的样子,而被完全淹没的、面积小的海岛则从此消失。

图3-15 侵蚀、稀释操作

经过侵蚀、稀释操作后,图中的黑色部分将会完全连在一起,面积较小的噪点则会被完全淹没在背景中。此时,下面的两个黑色圆形物体正是我们需要识别的球。现在先不着急,因为OpenCV的圆形识别算法识别的并非圆形物体,而是圆形的线条。因此还需要将这张图片的边缘提取出来,提取边缘的方法就是对整张图片计算梯度,此时如果一张像素周围全是黑色或者全是白色,则梯度为0;而旁边既有黑色又有白色,则会产生一个颜色梯度,即图片边缘。我们可以用Sobel算子分别对图像的横向、纵向计算颜色梯度,然后求平方根,得到总梯度。Sobel算子是一个3×3的卷积核,定义如下:

计算过程如下:

Opencv-python的对应代码如下:

    sobelx = cv2.Sobel(img_ede*255, cv2.CV_64F, 1, 0)
    sobely = cv2.Sobel(img_ede*255, cv2.CV_64F, 0, 1)
    img_sob = np.sqrt(sobelx**2+sobely**2).astype(np.uint8)
    plt.imshow(img_sob)

其结果如图3-16所示。

图3-16 对图3-15的结果使用Sobel算子找出其边缘位置所在

此时已经得到了圆形的边缘,接下来调用OpenCV的圆形检测函数——cv2.HoughCircle,用Hough变换提取图中的圆形物体即可,如图3-17所示。

图3-17 成功识别图3-14中球所在的位置

最后多说几句:

(1)Hough变换可以在图中检索线段以及圆形,具体原理与利用极坐标系来表示线段、圆上的点有关,大家可以另行学习。常见的应用场景包括自动驾驶系统检测道路线(直线检测),以及显微镜下观察细胞(圆形检测)。

(2)通常情况下,Hough变换可以直接用在灰度图上,即上一个代码框里的gray = img_sob可以是gray = img_gray。这里之所以没有这么做,是因为背景部分同样有很多类似圆形的物体,造成了很多干扰,可能需要调试很多参数才能得到想要的结果,读者不妨试一试。如果通过颜色选择将特征二值化,进而通过侵蚀、稀释操作进一步去除背景噪声之后,可以让输入的图形更加简单,更加方便参数的调试。