背景消除或背景减法是这样一种假设。我们有2个图片,一个是静止的,比如场景,没有需要检测的东西,另一个照片则包含了要检测的对象,但他是侵入了背景里的东西,或对象。我们就是要检测这个东西,比如商场进入的小偷,老鼠,或者马路上通过的车辆。

利用背景减法,我们容易找到我们感兴趣的东西。先看看下面2张图片:


右边图片是我们的背景,左边图片是我们的结果,我们找到感兴趣的部分,就是框起来的部分。框起来前就是我们对比的图片,或者叫变化的图片。有这个教授坐在椅子上的部分。

本程序除了opencv 要安装好外,还要装好imutils。imutils 的下载和安装在 Python 下应用opencv 的简单功能演示 一文中有介绍。

代码开始部分

注释里介绍使用方法:

python image_sub.py --bg 背景文件名  --fg 前景文件名

然后输入必要的包,命令行参数处理,这里有缺省参数,你可修改default 后的文件路径和名

# USAGE 使用方法
# python image_sub.py --bg images/bg.jpg --fg images/adrian.jpg
# import the necessary packages 输入必要的包
import numpy as np
import argparse
import imutils
import cv2
# construct the argument parser and parse the arguments
# 命令行参数处理,2个图片都存在imges 目录里,这里提供缺省值
# 这根据你的情况,更改default 后的文件名,当然也可命令行输入
ap = argparse.ArgumentParser()
ap.add_argument("-b", "--bg", default='images/bg.jpg',
help="path to background image")
ap.add_argument("-f", "--fg", default='images/adrian.jpg',
help="path to foreground image")
args = vars(ap.parse_args())
导入图片并灰度化处理
# load the background and foreground images
# 导入背景,前景文件
bg = cv2.imread(args["bg"])
fg = cv2.imread(args["fg"])
# convert the background and foreground images to grayscale
# 灰度化处理
bgGray = cv2.cvtColor(bg, cv2.COLOR_BGR2GRAY)
fgGray = cv2.cvtColor(fg, cv2.COLOR_BGR2GRAY)

背景减法

做减法时,转换为int32,这样可以有负值。然后取绝对值,再转成类型uint8, opencv可以识别。


# perform background subtraction by subtracting the foreground from
# the background and then taking the absolute value
# 背景减法
sub = bgGray.astype("int32") - fgGray.astype("int32")
sub = np.absolute(sub).astype("uint8")
cv2.imshow("sub",sub)

二值化处理

用Otsu 门槛法,转换上面的减法结果为前景和背景,0为背景,255为前景。图片效果为下面左边图。

然后我们erosion,再 dilate消除噪声,处理效果为下面右边图:

erosion dilate 的详细介绍可以看:https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_imgproc/py_morphological_ops/py_morphological_ops.html


# perform a series of erosions and dilations to remove noise
# erode ,dilate 降噪处理
thresh = cv2.erode(thresh, None, iterations=1)
thresh = cv2.dilate(thresh, None, iterations=1)
cv2.imshow("thresh2",thresh)
发现各个边界 ,然后计算所有边界的范围
# find contours in the thresholded difference map and then initialize
# 发现边界
# our bounding box regions that contains the *entire* region of motion
cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
#给边界初始值
(minX, minY) = (np.inf, np.inf)
(maxX, maxY) = (-np.inf, -np.inf)
# loop over the contours
# 循环计算边界
for c in cnts:
# compute the bounding box of the contour
(x, y, w, h) = cv2.boundingRect(c)
# reduce noise by enforcing requirements on the bounding box size
# 如果边界值,w 或 w 小于20 就认为是噪音
if w > 20 and h > 20:
# update our bookkeeping variables
minX = min(minX, x)
minY = min(minY, y)
maxX = max(maxX, x + w - 1)
maxY = max(maxY, y + h - 1)
绘制长方形,并输出图形
# draw a rectangle surrounding the region of motion
# 绘制长方形
cv2.rectangle(fg, (minX, minY), (maxX, maxY), (0, 255, 0), 2)
# show the output image
# 输出图形
cv2.imshow("Output", fg)
cv2.imshow("bg", bg)
cv2.waitKey(0)
综合在一起的代码:
# USAGE 使用方法
# python image_sub.py --bg images/bg.jpg --fg images/adrian.jpg
# import the necessary packages 输入必要的包
import numpy as np
import argparse
import imutils
import cv2
# construct the argument parser and parse the arguments
# 命令行参数处理,2个图片都存在imges 目录里,这里提供缺省值
# 这根据你的情况,更改default 后的文件名,当然也可命令行输入
ap = argparse.ArgumentParser()
ap.add_argument("-b", "--bg", default='images/bg.jpg',
help="path to background image")
ap.add_argument("-f", "--fg", default='images/adrian.jpg',
help="path to foreground image")
args = vars(ap.parse_args())
# load the background and foreground images
# 导入背景,前景文件
bg = cv2.imread(args["bg"])
fg = cv2.imread(args["fg"])
# convert the background and foreground images to grayscale
# 灰度化处理
bgGray = cv2.cvtColor(bg, cv2.COLOR_BGR2GRAY)
fgGray = cv2.cvtColor(fg, cv2.COLOR_BGR2GRAY)
# perform background subtraction by subtracting the foreground from
# the background and then taking the absolute value
# 背景减法
sub = bgGray.astype("int32") - fgGray.astype("int32")
sub = np.absolute(sub).astype("uint8")
cv2.imshow("sub",sub)
# threshold the image to find regions of the subtracted image with
# larger pixel differences
thresh = cv2.threshold(sub, 0, 255,
cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
cv2.imshow("thresh",thresh)
# perform a series of erosions and dilations to remove noise
# erode ,dilate 降噪处理
thresh = cv2.erode(thresh, None, iterations=1)
thresh = cv2.dilate(thresh, None, iterations=1)
cv2.imshow("thresh2",thresh)
# find contours in the thresholded difference map and then initialize
# 发现边界
# our bounding box regions that contains the *entire* region of motion
cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
#给边界初始值
(minX, minY) = (np.inf, np.inf)
(maxX, maxY) = (-np.inf, -np.inf)
# loop over the contours
# 循环计算边界
for c in cnts:
# compute the bounding box of the contour
(x, y, w, h) = cv2.boundingRect(c)
# reduce noise by enforcing requirements on the bounding box size
# 如果边界值,w 或 w 小于20 就认为是噪音
if w > 20 and h > 20:
# update our bookkeeping variables
minX = min(minX, x)
minY = min(minY, y)
maxX = max(maxX, x + w - 1)
maxY = max(maxY, y + h - 1)
# draw a rectangle surrounding the region of motion
# 绘制长方形
cv2.rectangle(fg, (minX, minY), (maxX, maxY), (0, 255, 0), 2)
# show the output image
# 输出图形
cv2.imshow("Output", fg)
cv2.imshow("bg", bg)
cv2.waitKey(0)