当我们要把一幅图像中的运动区域和静止区域区分开的时候,这样的任务在计算机视觉中称为前后景分离,而帧差法则是前后景分离中最简单的一种方法,单纯考虑像素值在空间上的变化而不考虑时间特性。
对于两帧帧差法的计算过程可以用一句话描述:对于某个像素,如果它在前后两幅图像中的差值的绝对值超过某个设定好的阈值,则认为它属于前景,否则认为它属于背景。
具体的 Python 代码如下,使用了numpy和opencv:
''' 函数名: frame_diff 输入: img1 int类型的灰度图 img2 int类型的灰度图 thresh 帧差阈值 输出: 前景为白色,背景为黑色的掩码图 ''' def frame_diff(img1, img2, thresh): height, width = img1.shape[:2] # 获取宽高 mask = np.zeros((height, width), dtype=np.uint8) # 返回的掩码图 # 遍历图像 for i in xrange(height): for j in xrange(width): if abs(img1[i,j] - img2[i,j]) > thresh: # 差的绝对值大于阈值 mask[i,j] = 255 else: mask[i,j] = 0 return mask
设定阈值为50,对两幅实例图像处理的结果如下:
理想情况下,我们得到的掩码图应该是中间挡住 lena 的一整块全白,这是因为有些像素点差值依然小于阈值,造成了一些“空洞”,在实际应用中会带来问题,但也只能用如膨胀之类的方法减小这些空洞。
即使存在需要人为设定阈值和会造成空洞这样的问题,也不能阻止我们用它来做一些应用。比如在你家安置这样一个摄像头,在你离开的这段时间,如果检测到一大片移动区域(掩码图中白色的像素值占全图的比例很高),那么就可以触发一些异常报警。其实很多市场上的智能摄像头的移动侦测功能就是这么做的。
其实帧差法在我个人的工作中更多是一个预处理的手段,比如我会对掩码图中的各个连通区域做最大外接矩形把这些区域都单独标记出来,再对每个矩形区域做其他的处理,比如判断它是不是个人。
最后,其实上面写的代码很不 Python,只是为了解释清原理写的,真正的写法应该这样:
def frame_diff2(img1, img2, thresh): mask = np.zeros(img1.shape, dtype=np.uint8) # 返回的掩码图 mask[np.abs(img1 - img2) > thresh] = 255 return mask
最后奉上完整代码:
# coding: utf-8 import os, sys import cv2 import numpy as np ''' 函数名: frame_diff 输入: img1 int类型的灰度图 img2 int类型的灰度图 thresh 帧差阈值 输出: 前景为白色,背景为黑色的掩码图 ''' def frame_diff(img1, img2, thresh): height, width = img1.shape[:2] # 获取宽高 mask = np.zeros((height, width), dtype=np.uint8) # 返回的掩码图 # 遍历图像 for i in xrange(height): for j in xrange(width): if abs(img1[i,j] - img2[i,j]) > thresh: # 差的绝对值大于阈值 mask[i,j] = 255 else: mask[i,j] = 0 return mask def frame_diff2(img1, img2, thresh): mask = np.zeros(img1.shape, dtype=np.uint8) # 返回的掩码图 mask[np.abs(img1 - img2) > thresh] = 255 return mask # 程序入口 def main(): # 读图 img1 = cv2.imread('lena.jpg', 0) img2 = cv2.imread('lena_tmpl.jpg', 0) img1_int = img1.astype(np.int32) img2_int = img2.astype(np.int32) # 帧差法获取掩码图 mask = frame_diff2(img1_int, img2_int, 50) # 显示 cv2.imshow('img1', img1) cv2.imshow('img2', img2) cv2.imshow('mask', mask) cv2.imwrite('img1.jpg', img1) cv2.imwrite('img2.jpg', img2) cv2.imwrite('mask.jpg', mask) cv2.waitKey(0) if __name__ == '__main__': main()
后语
今天就说到这里,小钊平时工作 996 非常忙(人家高薪哇,酸溜溜),以下奉上我的公众号二维码,对 Python 、算法有兴趣的可以关注一下。