机器视觉(二)背景减法

我们再来看一个图像减法的应用,这个应用叫做背景减法。我们之前讨论过的图像减法应用是颜色减法,这种方法只有在我们知道所寻找对象的颜色时才有效。而背景减法则不同,即使我们不知道所寻找对象的颜色,它也能起作用。但是,背景减法只在图像非常静止且我们寻找的对象进入或离开视野时才有效。让我们通过编写一些代码来学习背景减法的工作原理。

在背景减法中,我们只想看到视野中新的或不同的东西。我们可以通过拍摄一张照片,将其保存为某个变量,然后等到一个新对象进入视野,再拍摄一张新照片。我们可以将旧图像减去新图像,这样就会得到第三张图像,显示两张图像之间的不同之处。让我们编写一些代码来实现这一点。首先,我们要改变的是,不再将图像分解为各个颜色分量,而是使用一个内置的OpenCV函数将彩色图像转换为灰度图像,这个函数叫做cvtColorcvtColor需要两个输入参数:我们想要转换的图像和一个算法调用,告诉它我们想要如何转换颜色。我们将使用COLOR_BGR2GRAY,使用OpenCV内置的红绿蓝到灰度转换。这种转换不会将图像分解为各个颜色分量,而是以一种模拟人眼感知颜色的方式进行转换。

gray_image1 = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

接下来,我们在这个循环中需要做的是显示这张图像。注意,我称这张图像为grayImage1,因为我们稍后会有另一张图像叫做grayImage2,我们希望减去这两张图像。在显示图像后,我们立即进入等待用户按下Escape键的代码部分。当用户按下Escape键后,我们仍然有这个变量grayImage1保存着当前的图像,从现在开始我们将其称为背景图像。接下来,让我们进入另一个捕捉图像的循环。这里我们将做与之前相同的事情,将新图像转换为灰度图像,这次我们称之为grayImage2

gray_image2 = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

我们显示grayImage2,然后我们希望计算新灰度图像grayImage2和旧灰度图像grayImage1之间的差异,我们称这个变量为difference。我们可以从设置difference等于grayImage1减去grayImage2开始,但正如我们在上一个视频中所学,这样做不会完全正确,因为这两张图像是无符号8位整数。我们最好将它们设置为有符号16位整数,这样我们就可以处理可能出现的负数。接下来我们确保将每个变量设置为矩阵,以便我们可以对它们进行矩阵操作。

Difference = np.absolute(np.matrix(np.int16(gray_image1)) - np.matrix(np.int16(gray_image2)))

最后,我不知道我要找的对象是比背景更亮还是更暗的背景。我可以通过取这个差值的绝对值来考虑这两种可能的情况。现在,我仍然需要使差值矩阵成为有效的图像矩阵,所以我们说差值中每个大于255的元素都应该等于255。我不需要在这里添加另一行代码来说明每个小于零的元素都应该设置为零,因为我们已经将差值设为绝对值,所以它们已经全是正数或零了。现在,让我们将差值矩阵转换为无符号8位整数,并显示差值矩阵。最后,我们有一段代码,等待用户按下Escape键以退出这个循环。

 Difference[Difference > 255] = 255
 Difference = np.uint8(Difference)

现在,所有关于计算对象列位置的内容都放在第二个循环之后。我们要做这个计算的图像实际上是差值图像,所以我们在这里使用difference。好,让我们来测试一下。测试时,先清除相机视野中的所有物体,然后运行这个模块。这里我们看到一张来自相机的图像,它是灰度图像,不是特定颜色,只是图像的灰度转换。现在,当我们按下Escape键时,摄像机会捕捉当前图像作为背景图像,所以尽量保持静止,不要让相机晃动太多,并确保视野中没有物体,然后按下Escape。

现在你可以看到前景图像,这是这两张图像之间的差异。

图片

现在,如果我按下Escape键,我们的代码应该已经计算出列位置,这次我将对象放在屏幕的一侧,因此列位置应该是600左右,实际是360左右,这并不是很准确。

图片

为了解释为什么我们有一些准确性问题,我必须首先指出机器视觉领域和日常用语之间的术语差异。很久以前,我们有电视节目没有颜色,我们可能会称这些电视为黑白电视。在机器视觉中,这些节目实际上不是黑白节目,而是灰度节目。术语“黑白”实际上指的是每个像素只有一位的图像,这一位可以是零,表示完全暗,或一,表示完全亮。

当我们在这个背景减法问题中看到灰度差值图像时,它看起来像是我们放置的对象非常亮,而背景是完全暗的,但这并不是真的。房间内光线水平的各种变化、相机的小幅晃动等都会在整个图像中产生小幅灰色像素,这些灰度像素会影响我们对对象位置的计算。我们可以使用一种称为阈值化的方法将我们的图像从灰度图像转换为实际的黑白图像来解决这个问题。我将向你展示如何做到这一点。我们首先创建一个变量BW表示黑白图像,并将其设置为差值图像,除非我们现在要选择一些阈值水平,每个高于该阈值水平的像素将转换为数字1,每个低于该阈值水平的像素将转换为数字0。我们这样做,BW每个元素如果BW大于100,我们将使用100作为我们的初始阈值,则等于1。同时,BW每个地方如果BW小于或等于100,则等于0。注意,按这个顺序进行操作很重要。如果你首先检查所有大于100的像素并将其设为1,那么所有这些像素将被第二个操作捕获,该操作检查所有小于100的像素。所以请确保你首先捕获所有小于或等于100的像素,将它们设为0,然后检查所有大于100的像素,并将它们设为1。

 BW=Difference
 BW[BW<=100] = 0
 BW[BW>100] = 1

现在,在我们计算列位置的地方,不要在差值上做,而是在BW上做。

total_total = np.sum(np.sum(BW))

让我们尝试运行这个代码,当你准备好时,确保背景中没有物体,按下Escape键。现在视野中没有物体,对象的位置被计算为零。让我们将一个物体放入视野中,现在将对象移到屏幕的一侧,这里我们得到一个很低的值,表示对象在屏幕的左侧,这里我们得到一个接近640的高值,表示对象接近屏幕的右侧。所以我们的阈值化方法通过消除背景中我们不想计算的许多中灰像素,极大地帮助了我们。

图片
图片

现在注意,这里显示的位置仅告诉我们X的位置,如果我们想知道对象的上下位置,我们需要对行做与列相同的计算。

附完整程序代码:

import cv2
import numpy as np

# Initialize the video capture
cap = cv2.VideoCapture(0)

while True:
    _,frame = cap.read()
    gray_image1 = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    cv2.imshow('background',gray_image1)
    k= cv2.waitKey(5)
    if k==27:
        break

while True:
    # Read a new frame
    _, frame = cap.read()
    
    # Convert the new frame to grayscale
    gray_image2 = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    
    # Show the foreground (current frame)
    cv2.imshow('foreground', gray_image2)


    
    # Calculate the absolute difference between the background and current frame
    Difference = np.absolute(np.matrix(np.int16(gray_image1)) - np.matrix(np.int16(gray_image2)))
    Difference[Difference > 255] = 255
    Difference = np.uint8(Difference)
    # Show the difference
    cv2.imshow('difference', Difference)

    BW=Difference
    BW[BW<=100] = 0
    BW[BW>100] = 1

    # Calculate column sums and perform analysis
    column_sums = np.matrix(np.sum(Difference, 0))
    column_numbers = np.matrix(np.arange(frame.shape[1]))  # Assuming 640 is the width of the frame
    column_mult = np.multiply(column_sums, column_numbers)
    total = np.sum(column_mult)
    total_total = np.sum(np.sum(BW))
    column_location=total/total_total

    print(column_location)    


    k= cv2.waitKey(5)
    if k==27:
        break

cv2.destroyAllWindows()
cap.release()