【OpenCV 学习笔记】第二十一章: 图像及视频去背景

第二十一章: 图像及视频去背景

我们做目标识别、目标检测的时候经常需要去背景,比如车辆检测,就是摄像头拍摄一段车辆行驶视频,统计一下视频里面的车流量,此时我们首先要识别出图片中的车辆才能计数有多少辆车,而要识别车辆就需要先把车辆从图像中分割出来再做识别,而把车辆分割出来就是将前景物体从背景中分离出来,就是我们需要把视频的背景全部去掉,只剩下车辆,然后再进行其他操作。
所以去背景就是前后景分割和提取,去背景的方法和手段有很多,我们第十七章也详细讲了两种去背景的算法:分水岭算法和GrabCut算法。本章讲meanshift算法几种视频去背景算法

一、MeanShift算法

meanshift算法本是机器学习中聚类算法中的一个算法,就是一种无监督分类算法,就是把图像的所有像素点看成一个个没有标签的样本数据,然后用探索这些样本数据的内部规律,把所有样本自动分成若干个类别,实现自动聚类。如果我们对机器学习算法中的聚类算法比较熟悉,那就很容易理解meanshift算法。但这个算法的亮点是用在图像处理中,这个算法搭配canny算法可以得到效果更加好的边缘检测效果;搭配分水岭算法可以得到更好的图像分割效果;搭配轮廓检测函数cv2.findContours()可以得到更好的轮廓效果;搭配直方图可以得到更好的匹配效果,可以用于视频中的运动跟踪了。

meanshift算法运用到图像上就是:以图像上任一点为圆心p,半径为sp,色彩幅值为sr,进行不断地迭代,实现像素点地自动聚类。实现效果如下:

 从上图我们可以看到图像上的颜色都变成一片一片的了,那是因为算法将色彩分布相近的颜色聚为一类,并且都用该类的中心点像素值代替了。
所以meanshift可以很好的平滑掉图像上彩色细节,侵蚀掉面积较小的颜色区域,色彩分布相近的颜色都变成一片片了,此时要做分割就可以很好的分割了,要提取轮廓就可以很好的提取了。所以这个算法搭配其他算法可以更好的实现图像分割、前后景提取、视频跟踪等功能。

meanshift算法的原理其实非常简单,其中的数学推导和代码实现也不是很复杂,网上有大量的资料可供我们参考,但是,把这个机器学习算法用到图像处理中,其中涉及到的细节还是非常多的:
1、改进后的meanshift算法又加入了核函数,核密度估计要了解;
2、机器学习中的meanshift算法是不断画圆-找质心-迭代,从而实现自动聚类,但是对于图像数据来说,就不是画圆迭代而是画球迭代,因为除了设置像素点之间的物理空间坐标半径外,还需要设置色彩幅值的半径,就是一个空间球体的不断迭代;
3、为了实现尺度不变性,opencv中该算法还结合了图像金字塔,其参数中就有一个专门定义所需金字塔层数的变量,所以我们还需要对图像金字塔有所了解;
4、用于计算的像素点的色彩空间,我们一般不用RGB而是转化为luv色彩空间,因为我们在计算色彩距离的时候一般用欧式距离,而计算出来的结果不一定符合人眼的色彩距离,比如两个色彩颜色我们人类觉得很近似,如果用RGB计算这两个色彩之间的欧式距离,结果很大,不符合人眼的规律,如果用luv计算欧式距离,人眼看着颜色相近,计算结果就越小,人眼看着颜色相差越大,计算结果就越大,符合人类视觉规律。一句话,Luv色彩空间是和人类视觉统一的,所以计算的时候要转化为Luv。
。。。。牵扯到的细节非常多,所以本章只讲opencv中的meanshift算法api,以后写机器学习算法了,再详细写这个算法的原理、数学推导以及代码实现。

  • API:cv2.pyrMeanShiftFiltering(src, sp, sr[, dst[, maxLevel[, termcrit]]])
    scr:输入的图像,8位3通道彩图。但是这里并不要求必须是RGB格式,BGR,HSV等都可以,算法在底层都统一转化为Luv进行计算;
    sp:为像素点物理坐标空间半径的大小。sp设置得越高,模糊程度就越大,sp设置得越小,图像模糊程度就越小。
    sr:为像素点色彩空间的半径大小,也就是色彩幅值的范围,就是规定颜色相近到多少才算作同类颜色。sr设置的越高,能模糊的差异就越大,sr设置的越低,颜色相近度就要求得越高。
    dst:输出图像,跟输入src有同样的大小和数据格式;
    maxLevel:定义金字塔的最大层数;默认值是1,就是不用下采样数据。
    termcrit:定义的漂移迭代终止条件,可以设置为迭代次数满足终止,迭代目标与中心点偏差满足终止,或者两者的结合;

  • pyrMeanShiftFiltering()函数的执行过程
    1、构建迭代空间。
    以输入图像上任一点P0为圆心,建立以sp为物理空间半径,sr为色彩空间半径的球形空间,物理空间上坐标为x和y,色彩空间上坐标为RGB或Luv(最好)或HSV,构成一个空间球体。其中x和y表示图像的长和宽,色彩空间R、G、B在0至255之间。
    2、求迭代空间的向量并移动迭代空间球体重新计算向量,直至收敛。
    在上一步构建的球形空间中,求出所有点相对于中心点的色彩向量之和,移动迭代空间的中心点到该向量的终点,并再次计算该球形空间中所有点的向量之和,如此迭代,直到在最后一个空间球体中所求得向量和的终点就是该空间球体的中心点Pn,迭代结束。
    3、更新输出图像dst上对应的初始原点P0的色彩值为本轮迭代的终点Pn的色彩值,完成一个点的色彩均值漂移。
    4、对输入图像src上其他点,依次执行上述三个步骤,直至遍历完所有点后,整个均值偏移色彩滤波完成。

#例21.1 调用pyrMeanShiftFiltering()函数对图像进行色彩平滑,然后提取边缘、轮廓、前景对象
import cv2
import numpy as np

img = cv2.imread(r'C:\Users\25584\Desktop\rabbit.jpeg')    #原图
mean_img = cv2.pyrMeanShiftFiltering(img, 50, 30)          #色彩平滑,后面两个参数可以根据自己的效果进行调整

#---------------------提取边缘和轮廓-------------------------------------------------------------------------------
canny_img = cv2.Canny(mean_img, 120, 200)                                                       #canny提取边缘
contours, hierarchy = cv2.findContours(canny_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)   #提取轮廓
print(len(contours))      #打印轮廓个数
contours_ = []           #用面积过滤掉小的轮廓
for i in contours:
    if cv2.contourArea(i)>=20:
        contours_.append(i)
print(len(contours_))   #打印出轮廓个数,看剩几个轮廓了
huabu = np.zeros(img.shape, dtype=np.uint8)  #把过滤后的轮廓画出来
contours_img = cv2.drawContours(huabu, contours_, -1, (255,255,255), 1)

#-------------------提取前景--------------------------------------------------------------------------------------
gray_img = cv2.cvtColor(mean_img, cv2.COLOR_BGR2GRAY)   #转灰度图像
_, mask = cv2.threshold(gray_img, 127, 255, cv2.THRESH_BINARY)  #用阈值转成二值图像,mask.shape是(400, 600)
img_pre = cv2.bitwise_and(img, cv2.merge([mask,mask,mask]))     #按位与运算,也就相当于img和掩码mask进行运算,mask是一个二维的,用merge函数升到3维,再与img与运算

#可视化
fig, axes = plt.subplots(1,6, figsize=(25,16), dpi=100)   #可视化
axes[0].imshow(img[:,:,::-1]), axes[0].xaxis.set_ticks([]), axes[0].yaxis.set_ticks([]), axes[0].set_title('original')                #原图图像
axes[1].imshow(mean_img[:,:,::-1]), axes[1].xaxis.set_ticks([]), axes[1].yaxis.set_ticks([]), axes[1].set_title('meanshift')          #meanshift后的图像
axes[2].imshow(canny_img, cmap='gray'), axes[2].xaxis.set_ticks([]), axes[2].yaxis.set_ticks([]), axes[2].set_title('canny')          #canny边缘检测的图像
axes[3].imshow(contours_img, cmap='gray'), axes[3].xaxis.set_ticks([]), axes[3].yaxis.set_ticks([]), axes[3].set_title('contour')     #经过面积过滤的轮廓
axes[4].imshow(mask, cmap='gray'), axes[4].xaxis.set_ticks([]), axes[4].yaxis.set_ticks([]), axes[4].set_title('mask')                #掩膜图像
axes[5].imshow(img_pre[:,:,::-1]), axes[5].xaxis.set_ticks([]), axes[5].yaxis.set_ticks([]), axes[5].set_title('pre_img')              #前景
plt.show()

二、视频去背景

视频去背景又叫视频背景扣除,也叫视频前后景分割和提取。
静态图像去背景和视频去背景在算法上还是有区别的。对于视频来说,是以时间轴为顺序,每张图片前后有一个关联关系。如果图片的像素在一段时间内不发生变化,我们就认为这个像素是背景色,如果发生频繁的变化,我们就认为是前景色。这是视频去背景算法的原理,但也会有一些缺点,比如一棵树在风吹的时候,那这颗树就从背景识别成了前景。再比如我们的影子,人动影子就动,所以影子也很容易就被识别为前景色。再比如动着的人如果不动了,那这个人就又从前景变为背景了,就被扣掉了,就是静止的人就检测不出来,动的人才能检测出来。

opencv给我们提供了多种视频去背景方法,我们介绍下面几种方法:

  • 1、MOG去背景API:cv2.bgsegm.createBackgroundSubtractorMOG([history, nminxtures, backgroundRatio, noiseSigma])
    creatBackgroundSubtractorMOG()是以混合高斯模型为基础的前后景分割算法,是一个类,放在opencv下面的一个子包bgsegm里,我们可以通过这个类创建一个mog对象
    history:表示建模时需要多长时间的参考帧,默认是200毫秒。假设视频的播放速度是一秒钟显示25张图片,也就是25帧,那么建模参考的图片就是5张。
    nminxtures:高斯值范围,默认是5,就是把一张图片分成5x5个小块,每个小块都有一个高斯值,然后算出一个参考模型
    backgroundRatio:背景比例,就是背景在整张图片中的占比,默认是0.7,就是默认一张图中70%是背景。
    noiseSigma:噪音的参数,这个参数设置为0就是自动降噪,默认值也是0
    一般情况下,我们使用这个类时,一般都用默认参数即可,默认值下效果就比较不错了。

  • 2、MOG2去背景API:cv2.createBackgroundSubtractorMOG2([history, detectShadows])
    MOG2是对MOG算法的一个改进,因为MOG对光线下产生的阴影无法识别,MOG2可以对阴影有更好的识别,这对我们计算准确度是非常关键的。
    但是MOG2的缺点是前后景分离时会产生很多噪点。MOG2在opencv的标准库中所以直接调用。
    history:默认500毫秒
    detectShadows:是否检测阴影,默认值是True,就是默认把阴影也检测出来

  • 3、GMG去背景API:cv2.bgsegm.createBackgroundSubtractorGMG([initializationFrames])
    GMG是针对MOG2噪点过多而诞生的,GMG采用静态背景图像估计和每个像素的贝叶斯分割,既可以检测出阴影还具有更强的抗噪性。GMG也没有放在标准库中。
    initializationFrames:初始化的帧数,默认是120。MOG和MOG2都是先缓存200毫秒的时间,而GMG是缓存120个帧数,如果你的视频帧率很大,那缓存的时间就非常短,如果视频帧率很小,比如20帧/秒,就要缓存6秒才能开始视频,这是二者的最大区别,也是GMG的最大缺点,就是开始了好长时间没有任何信息显示,但是这个缺点我们可以通过调整参数initializationFrames来避免,但是这个参数又不能调太小,太小了计算的参考帧数太少,效果又不好。

opencv中去背景的相关论文: An improved adaptive background mixture model for real-time tracking with shadow detection

#例21.2 调用上面几种方法对视频进行前后景分离,对比这几种方法的优缺点  
import cv2
import numpy as np

cap = cv2.VideoCapture(r'C:\Users\25584\Desktop\vtest.avi')  #加载视频
mog1 = cv2.bgsegm.createBackgroundSubtractorMOG()  #创建mog对象
mog2 = cv2.createBackgroundSubtractorMOG2()
mog3 = cv2.bgsegm.createBackgroundSubtractorGMG(30)   #初始化帧数取30吧

while True:
    ret, frame = cap.read()
    if ret:
        fgmask1 = mog1.apply(frame)  #传入frame就生成一个前景对象的掩码
        fgmask2 = mog2.apply(frame) 
        fgmask3 = mog3.apply(frame) 
        cv2.imshow('img1', fgmask1)  #显示前景对象
        cv2.imshow('img2', fgmask2)
        cv2.imshow('img3', fgmask3) 
    else:
        break
        
    k = cv2.waitKey(10)    #设置退出
    if k == 27:
        break
cap.release()   #释放资源
cv2.destroyAllWindows()

猜你喜欢

转载自blog.csdn.net/friday1203/article/details/125537538