国外课栈 - 国外电子信息技术视角栈

聚焦Python和OpenCV视觉跟踪显着对象

 二维码 11
Python      OpenCV
聚焦Python和OpenCV‍全文档‍

准备

本文使用到OpenCV 4.1.0,NumPywxPython 2.8Matplotlib。尽管本文中的部分算法已添加到OpenCV 3.0.0版本的可选Saliency模块中,但目前尚无Python API,因此我们将编写自己的代码。

了解视觉显着性

视觉显着性是认知心理学中的一个技术术语,旨在描述某些物体或物品的视觉质量,从而使它们立即引起我们的注意。 我们的大脑不断将视线移向视觉场景的重要区域,并随着时间的推移对其进行跟踪,从而使我们能够快速扫描周围的环境以找到有趣的物体和事件,而忽略次要的部分。

以下屏幕快照显示了一个常规RGB图像及其转换为显着性图的示例,其中统计上有趣的弹出区域显得明亮而其他区域则很暗。

傅立叶分析将使我们对自然图像统计数据有一个大致的了解,这将有助于我们建立一个一般图像背景的模型。 通过将背景模型与特定的图像框架进行比较和对比,我们可以找到从周围环境中弹出的图像子区域(如上一个屏幕截图所示)。 理想情况下,这些子区域与图像块相对应,这些块在观看图像时会立即引起我们的注意。

传统的模型可能会尝试将特定的特征与每个目标相关联,这会将问题转换为检测特定类别的对象。 然而,这些模型需要人工标记和训练。

取而代之的是,我们将尝试模仿大脑的行为,即将算法调整为自然图像的统计数据,以便我们可以立即在视觉场景中找到“吸引我们注意力”的模式或子区域(即, 偏离这些统计规律的模式),并标记它们以进行进一步检查。 结果是一种适用于场景中任意数量的原型对象的算法,例如跟踪足球场上的所有运动员。 请参阅以下屏幕截图以查看实际效果:


正如我们在这四个屏幕截图中看到的那样,一旦找到了图像的所有潜在有趣斑点,我们就可以使用一种简单而有效的方法(称为对象均值漂移跟踪)在许多帧上跟踪它们的运动。 由于场景中可能有多个原型对象,这些对象可能会随着时间的流逝而改变外观,因此我们需要能够区分它们并跟踪所有它们。

构思App

要构建该应用程序,我们需要结合前面讨论的两个主要功能-显着性图和对象跟踪。 最终的应用程序会将视频序列的每个RGB帧转换为显着图,提取所有有趣的原型对象,并将其提供给均值漂移跟踪算法。 为此,我们需要以下组件:

  • main:这是启动应用程序的主要功能例程。
  • saliency.py:这是一个从RGB彩色图像生成显着性图和原始对象图的模块。 它包括以下功能:
    • get_saliency_map:这是将RGB彩色图像转换为显着图的功能。
    • get_proto_objects_map:这是将显着图转换为包含所有原型对象的二进制掩码的功能。
    • plot_power_density:此功能用于显示RGB彩色图像的二维功率密度,有助于理解傅里叶变换。
    • plot_power_spectrum:此功能用于显示RGB彩色图像的径向平均功率谱,有助于理解自然图像统计信息。
    • MultiObjectTracker:这是一个使用均值漂移跟踪来跟踪视频中多个对象的类。 它包括以下公共方法:
      • MultiObjectTracker.advance_frame:这是一种使用新帧的显着图上的均值偏移算法将框的位置从前一帧更新到当前帧的方法,从而为新帧更新跟踪信息。
      • MultiObjectTracker.draw_good_boxes:这是一种用于说明当前帧中跟踪结果的方法。

实现main函数

为了运行我们的应用程序,我们将需要执行main函数,该函数读取视频流的一帧,生成显着图,提取原型对象的位置,并从一帧到下一帧跟踪这些位置。

主处理流程由Chapter6.py中的主函数处理,该函数实例化跟踪器(MultipleObjectTracker)并打开一个视频文件,显示现场足球运动员的数量:

import cv2
from os import path
from saliency import get_saliency_map, get_proto_objects_map
from tracking import MultipleObjectsTracker
def main(video_file='soccer.avi', roi=((140, 100), (500, 600))):
 if not path.isfile(video_file):
   print(f'File "{video_file}" does not exist.')
   raise SystemExit
 # open video file
 video = cv2.VideoCapture(video_file)
 # initialize tracker
 mot = MultipleObjectsTracker()

然后,该函数将逐帧读取视频并提取一些有意义的感兴趣区域(出于说明目的):

while True:
 success, img = video.read()
 if success:
   if roi:
   # grab some meaningful ROI
   img = img[roi[0][0]:roi[1][0],roi[0][1]:roi[1][1]]

之后,感兴趣的区域将被传递到一个函数,该函数将生成该区域的显着性图。 然后,将基于显着图生成有趣的原型对象,然后将其与关注区域一起馈入跟踪器。 跟踪器的输出是输入区域,该输入区域用边界框注释,如前面的屏幕截图所示:

saliency = get_saliency_map(img, use_numpy_fft=False,gauss_kernel=(3, 3))
objects = get_proto_objects_map(saliency, use_otsu=False)
cv2.imshow('tracker', mot.advance_frame(img, objects))

该应用程序将在视频的所有帧中运行,直到到达文件末尾或用户按q键:

if cv2.waitKey(100) & 0xFF == ord('q'):
 break

了解MultiObjectTracker类

跟踪器类的构造函数很简单。 它所做的全部工作就是设置均值漂移跟踪的终止标准,并存储最小轮廓区域(min_area)和通过对象大小归一化的最小平均速度(min_speed_per_pix)的条件,以便在后续计算步骤中予以考虑:

def __init__(self, min_object_area: int = 400,
 min_speed_per_pix: float = 0.02):
 self.object_boxes = []
 self.min_object_area = min_object_area
 self.min_speed_per_pix = min_speed_per_pix
 self.num_frame_tracked = 0
 # Setup the termination criteria, either 100 iteration or move by
# least 1 pt
 self.term_crit = (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT,
 5, 1)

然后,用户可以调用advance_frame方法将新的帧提供给跟踪器。

但是,在使用所有这些功能之前,我们需要了解图像统计信息以及如何生成显着性图。

映射视觉显着性

视觉显着性试图描述某些对象或物品的视觉质量,从而使它们立即引起我们的注意。 我们的大脑不断地将视线移向视觉场景的重要区域,仿佛它在视觉世界的不同子区域上闪烁着手电筒一样,使我们能够快速扫描周围的环境来寻找有趣的物体和事件,而忽略那些次要的部分。

人们认为这是一种进化策略,用于应对在视觉上丰富的环境中不断出现的信息溢出问题。 例如,如果您在丛林中漫步,您希望能够在欣赏您面前蝴蝶翅膀上复杂的色彩图案之前注意到左侧灌木丛中的猛虎。 结果,视觉上显着的对象具有非凡的弹出效果,就像下面的屏幕截图中的目标栏一样:

但是,确定使这些目标弹出的视觉质量可能并不总是一件容易的事。 如果您正在查看左侧的图像,则可能会立即注意到图像中唯一的红色条。 但是,如果您正以灰度级查看此图像,则可能很难找到目标栏。

与颜色显着性相似,右侧图像中有一个视觉上显着的条。 尽管目标栏在左侧图像中具有唯一的颜色,在右侧图像中具有独特的方向,但是我们将这两个特征放在一起,突然间,唯一的目标栏不再弹出:

在前面的显示中,再次有一个唯一且与其他所有酒吧都不相同的酒吧。 但是,由于分散注意力项目的设计方式,引导您走向目标栏的引人注目之处很少。 相反,您发现自己似乎随机地扫描图像,寻找有趣的东西。

了解傅里叶分析

要找到图像的视觉显着子区域,我们需要查看其频谱。 到目前为止,我们已经处理了空间域中的所有图像和视频帧,即通过分析像素或研究图像强度在图像的不同子区域中如何变化。 但是,图像也可以在频域中表示,即通过分析像素频率或研究像素在图像中出现的频率和周期。

通过应用傅立叶变换,可以将图像从空间域转换到频域。 在频域中,我们不再考虑图像坐标(x,y)。 相反,我们旨在找到图像的光谱。 傅立叶的激进思想基本上可以归结为以下问题-如果将任何信号或图像转换成一系列圆形路径(也称为谐波)会怎样?

例如,想想彩虹。 美丽,不是吗? 在彩虹中,白色的阳光(由许多不同的颜色或光谱的一部分组成)散布在其光谱中。 在这里,当光线穿过雨滴时(类似于穿过玻璃棱镜的白光),太阳光的光谱会暴露出来。 傅立叶变换的目的是做同样的事情-恢复阳光中包含的光谱的所有不同部分。

对于任意图像可以实现类似的效果。 与频率对应于电磁频率的彩虹相反,对于图像,我们考虑空间频率,即像素值的空间周期性。 在监狱牢房的图像中,您可以将空间频率视为两个相邻监狱钢筋之间的距离(的倒数)。

从这种观点转变中可以获得的见解非常有力。 在不赘述的情况下,让我们仅说明一下,傅立叶频谱同时具有幅度和相位。 而幅度描述了不同数量图像中的频率,相位讨论这些频率的空间位置。 以下屏幕截图在左侧显示了自然图像,在右侧显示了相应的傅立叶幅度谱(灰度版本)。

右边的幅度谱告诉我们,在左边图像的灰度版本中,哪些频率分量最突出(明亮)。 调整频谱,使图像的中心对应于x和y中的零频率。 移到图像的边界越远,频率越高。 这个特殊的频谱告诉我们,左侧图像中有很多低频分量(聚集在图像中心附近)。

在OpenCV中,可以借助离散傅立叶变换(DFT)来实现此变换。 让我们构造一个完成任务的函数。 它包括以下步骤:

  1. 首先,如有必要,将图像转换为灰度。 该函数接受灰度和RGB彩色图像,因此我们需要确保对单通道图像进行操作:
  1. 我们将图像调整为最佳尺寸。 事实证明,DFT的性能取决于图像大小。 对于数量为2的倍数的图像,它往往最快。因此,通常用0填充图像是一个好主意:
  1. 然后我们应用DFT。 这是NumPy中的单个函数调用。 结果是复数的二维矩阵:
  1. 然后,将实数值和复数值转换为大小。 复数具有实部和复杂(虚部)部分。 要提取幅度,我们取绝对值:
    magn = np.abs(img_dft)
  1. 然后,我们切换到对数刻度。 事实证明,傅立叶系数的动态范围通常太大而无法在屏幕上显示。 我们有一些无法观察到的低值和高值变化。 因此,高值将全部显示为白点,而低值将全部显示为黑点。

    要使用灰度值进行可视化,我们可以将线性比例转换为对数比例:

    log_magn = np.log10(magn)
  1. 然后,我们移动象限,以使光谱在图像上居中。 这样可以更容易地目视检查幅度spectrum:
    spectrum = np.fft.fftshift(log_magn)
  1. 我们返回绘制结果:
    return spectrum/np.max(spectrum)*255

    可以用pyplot绘制结果。

了解自然场景统计

使用频谱残差法生成显着图

在场景中检测原型对象

了解均值漂移跟踪

自动跟踪足球场上的所有球员

了解有关OpenCV跟踪API的信息


阅读完整文档

文章分类: 编程碎语PythonOpenCV