机器视觉(一)图像减法与目标定位

我们了解到,图像在数学上表示为数字矩阵。这可能是机器视觉中最重要的基本概念,因为一旦我们认识到图像是数字,我们就意识到我们可以对图像执行数学运算。机器视觉领域的大部分工作都在于弄清楚我们可以对图像应用哪些数学运算和算法,以便获得我们从图像中提问的答案。

今天,我们将介绍一些在机器视觉中特别有用的简单数学运算。我们将从图像减法开始,并将其应用于两种不同的情况,两者的目的是在图像中隔离单个对象。然后,我们将介绍一种平均运算,这将帮助我们找到隔离对象的位置。

首先,让我们回顾一下之前的颜色示例。在这里,我们将图像分解为各个颜色成分。然而,我们的单色图像并没有有效地隔离单个对象。原因如下:注意,在所有这些图像(红色、绿色和蓝色)中,目标颜色在其图像中是明亮的,而在其他图像中是暗的。但有些颜色,尤其是白色,在所有三个图像中都是亮的。这是因为在光线中,白色是通过结合红色、绿色和蓝色光制成的。换句话说,一个完全红色的物体可能具有255、0、0的RGB值,使其在红色图像中显示为明亮,在其他两个图像中显示为暗。但一个完全白色的物体可能具有255、255、255的RGB值,使其在所有三个颜色图像中都显示为明亮。

图片

如果我们想得到一个只显示红色物体为明亮,而所有其他颜色(包括白色)都为暗的图像呢?我们可以通过使用图像减法来实现这一点。假设我们有一个红色像素,其RGB值为255、0、0,和一个白色像素,其RGB值为255、255、255。红色图像会显示两个完全明亮的像素(255、255),而绿色图像会显示两个像素,其中第一个是暗的,第二个是亮的(0、255)。假设我们然后将红色图像减去绿色图像。我们会得到什么?我们会得到两个像素:255、0。换句话说,红色像素保持明亮,但白色像素变暗。让我们在Python代码中尝试一下。

首先,在顶部,让Python理解我们要将这些图像视为矩阵,以便我们可以对它们进行矩阵数学运算。将np.matrix添加到每个红色、绿色和蓝色图像中。

red = np.matrix(red)
green = np.matrix(green)
blue = np.matrix(blue)

好了,现在让我们进行图像减法:

red_only = red - green

但我们代码中的写法有问题。记住,每个像素由一个8位数字表示。范围在0到255之间。假设图像中有一个像素是绿色的,因此红色值低而绿色值高。我们的8位值无法处理负数。如果我们现在不处理这个问题,我们会得到一些奇怪的结果。幸运的是,NumPy有一种简单的方法来改变数字的数字表示方式。让我们将这些矩阵分别表示为16位数字。这样我们将有16位来存储值,并且它们也可以是负数。顺便说一下,如果我们说一个值将存储为int,这意味着它可以是正数或负数。如果我们说它是uint,这意味着它只能是正数,因为“u”代表无符号。例如,如果一个变量是uint8,这意味着它有8位,并且范围可以从0到255,因为它只能是正数。如果我们说这个数字是int8,它仍然有8位,但现在它的范围可以从-127到+127,因为它是有符号的。

所以我们这样做:

red_only = np.int16(red) - np.int16(green)

这将强制将红色和绿色图像存储为int16值。好了,现在我想看看red_only图像。但现在red_only矩阵不是一个有效的图像矩阵;它可能有负数。为了使其作为图像有效,矩阵必须只有0到255之间的值。有一种简单的方法可以解决这个问题。让我们说red_only矩阵中每个小于零的元素应该被分配为值0。我们可以这样做:

red_only[red_only < 0] = 0

red_only矩阵中每个大于255的元素应该被分配为值255,像这样:

red_only[red_only > 255] = 255

最后,让我们显示red_only图像:

cv2.imshow('Red Only', red_only)

这是图形的标题,red_only是我们想在图像中显示的矩阵。现在让我们用我们的颜色形状片段测试一下。

现在,仍然有一些颜色,我们的红减绿解决方案将无法处理。假设一个物体有高的红色值,低的绿色值和高的蓝色值。例如,品红色会有高的红色和蓝色值,但绿色值低。我们可以通过减去蓝色矩阵来修复这个问题。在这里,我们将添加:

red_only = np.int16(red) - np.int16(green) - np.int16(blue)

现在我们在相机视图中有一个明亮的物体,其他所有东西都很暗,我们能找到这个物体的像素位置吗?一种方法是找到图像的亮度中心,就像我们可以找到物体的质量中心一样。这种方法的工作原理如下:假设我们有一个非常低分辨率的图像矩阵。这个矩阵表示一个明亮的物体在黑暗背景中。

图片

首先,我们将这个矩阵的列相加,像这样:

0 255 255 255

接下来,我们将每个这些总和乘以列号,然后相加:

0x1+255×2+255×3+255×4 = 2295

最后,我们取整个原始矩阵中所有值的总和并除以它,像这样。

2295/(255+255+255)=3

这个结果告诉我们图像的亮度中心为第三列。

显然,手动做这个是一个大麻烦。让我们看看如何将其添加到我们的Python代码中。算法的第一步是对图像矩阵的列求和。我们在这里使用的图像矩阵是red_only矩阵。让我们创建一个名为column_sum的变量,它将等于一个矩阵。然后我们需要对列求和,为此我们将使用NumPy的函数sum。函数sum接受一个输入,即我们要求和的矩阵,然后第二个输入是我们要沿着哪个轴求和值。如果我们这里放0,这意味着我们将对列求和。如果我们放1,我们将对行求和。在RGB图像中,我们甚至可以通过放2来对颜色求和。因为我们要对列求和,所以我们放0。

column_sum = np.sum(red_only, 0)

接下来,我需要创建一个向量来保存整个矩阵的列号。我将这个变量称为column_numbers。这也将是一个矩阵,在这里我们将使用NumPy函数arange。注意,这不是两个“r”的单词“arrange”。这是一个叫做arange的函数,它给我们一个数字范围,所以它只有一个“r”。输入是我们想要的数字数量,所以如果我们在这里写640,我们会得到一个从1到640的数字列表。

column_numbers = np.arange(640)

现在,我需要将column_sum乘以column_numbers。让我们创建一个名为column_mult的新变量,表示乘法。这里我们将使用NumPy的函数multiplymultiply做一些被称为元素乘法的操作。如果我们给multiply一个输入,它是两个向量或两个矩阵,我们得到的输出将是一个向量或矩阵,它具有与输入相同的维度,并包含该矩阵中每个元素的乘积。所以换句话说,np.multiply不做点积或叉积,它做的是元素乘法,这是我们在这里想要的。我们要乘的是column_sum乘以column_numbers

column_mult = np.multiply(column_sum, column_numbers)

好的,下一步是将这个结果中的所有乘法值加起来。所以我们创建一个名为total的变量,我们这样做:

total = np.sum(column_mult)

我们不需要在这里给出一个轴,因为column_mult是一个向量,它只有一个轴。现在我需要取这个total并除以整个原始图像矩阵的总和。所以让我们创建另一个变量,我将这个变量称为total_total。这是red_only矩阵中所有像素的总和,所以我们这样做:

total_total = np.sum(red_only)

内部的总和将对red_only的列求和,然后外部的总和将对总和求和,我们会得到一个值,这是red_only中所有值的总和。所以这个算法的最后一步是将total除以total_total

column_location = total / total_total

这样,当我们运行它时,column_location会显示在Python shell中。现在让我们测试一下。注意,当我将红色物体向左滑动时,column_location值变小,当我将红色物体向右滑动时,column_location值变大。最大值将是640,因为这是我总列数,最小值应接近零。请记住,相机视图中的手会影响结果,因为相机通常会将皮肤识别为红色。

图片
图片

我们现在已经学会了如何使用图像减法在屏幕上隔离特定颜色的物体,并且我们学会了如何使用质心方法来找到隔离物体的位置。在下一个视频中,我们将学习图像减法的另一个应用,即背景减法。

附全部python代码如下:

import numpy as np
import cv2

cap=cv2.VideoCapture(0)

while(1):
    _, frame=cap.read()
    
    red=np.matrix(frame[:,:,2])
    green=np.matrix(frame[:,:,1])
    blue=np.matrix(frame[:,:,0])

    red_only = np.int16(red) - np.int16(green) - np.int16(blue)

    red_only[red_only < 0] = 0
    red_only[red_only > 255] = 255
    red_only = np.uint8(red_only)

    column_sum = np.sum(red_only, 0)
    column_numbers = np.arange(640)
    column_mult = np.multiply(column_sum, column_numbers)
    total = np.sum(column_mult)
    total_total = np.sum(red_only)
    column_location = total / total_total

    print(column_location)
    
    cv2.imshow('rgb', frame)
    cv2.imshow('red', red)
    cv2.imshow('green', green)
    cv2.imshow('blue', blue)
    cv2.imshow('Red Only', red_only)
    
    k=cv2.waitKey(5)
    if k==27:
        break

cv2.destroyAllWindows()

print(frame)