运动背景分割法Background Segment主要是指通过不同方法拟合模型建立背景图像,将当前帧与背景图像进行相减比较获得运动区域。下图所示为检测图像:

通过前面的检测帧建立背景模型,获得背景图像。然后检测图像与背景图像相减即为运动图像,黑色区域为背景,白色区域为运动目标,如下图所示:

在OpenCV标注库中有两种背景分割器:KNN,MOG2。但是实际上OpenCV_contrib库的bgsegm模块中还有其他几种背景分割器。本文主要介绍OpenCV_contrib中的运动背景分割模型及其用法,并对不同检测模型的性能和效果进行对比。

1 方法介绍

OpenCV_contrib中bgsegm模块主要有GMG, CNT, KNN, MOG, MOG2, GSOC, LSBP等7种背景分割器,其中KNN,MOG2可以在OpenCV标准库中直接使用,其他需要在OpenCV_contrib库中使用。具体各个方法介绍如下:

  • GMG:基于像素颜色进行背景建模
  • CNT:基于像素点计数进行背景建模
  • KNN:基于K最近邻进行背景建模
  • MOG:基于混合高斯进行背景建模
  • MOG2:基于混合高斯进行背景建模,MOG的升级版本
  • GSOC:类似LSBP
  • LSBP:基于LBP进行背景建模

各个方法提出时间、相关论文和OpenCV函数接口介绍如下表所示:

方法 提出时间 OpenCV函数接口介绍
GMG 2012 BackgroundSubtractorGMG
CNT 2016 BackgroundSubtractorCNT
KNN 2006 BackgroundSubtractorKNN
MOG 2001 BackgroundSubtractorMOG
MOG2 2004 BackgroundSubtractorMOG2
GSOC 2016 BackgroundSubtractorGSOC
LSBP 2016 BackgroundSubtractorLSBP

OpenCV contrib库的编译安装见:

OpenCV_contrib库在windows下编译使用指南

2 代码与方法评估

2.1 代码

下述代码介绍了OpenCV_contrib的bgsegm模块中不同背景分割方法C++和Python的调用。对比了不同背景分割方法在示例视频下,单线程和多线程的效果。

代码和示例视频下载地址:

https://github.com/luohenyueji/OpenCV-Practical-Exercise

完整代码如下:

C++

  1. #include <opencv2/opencv.hpp>
  2. #include <opencv2/bgsegm.hpp>
  3. #include <iostream>
  4. using namespace cv;
  5. using namespace cv::bgsegm;
  6. const String algos[7] = { "GMG", "CNT", "KNN", "MOG", "MOG2", "GSOC", "LSBP" };
  7. // 创建不同的背景分割识别器
  8. static Ptr<BackgroundSubtractor> createBGSubtractorByName(const String& algoName)
  9. {
  10. Ptr<BackgroundSubtractor> algo;
  11. if (algoName == String("GMG"))
  12. algo = createBackgroundSubtractorGMG(20, 0.7);
  13. else if (algoName == String("CNT"))
  14. algo = createBackgroundSubtractorCNT();
  15. else if (algoName == String("KNN"))
  16. algo = createBackgroundSubtractorKNN();
  17. else if (algoName == String("MOG"))
  18. algo = createBackgroundSubtractorMOG();
  19. else if (algoName == String("MOG2"))
  20. algo = createBackgroundSubtractorMOG2();
  21. else if (algoName == String("GSOC"))
  22. algo = createBackgroundSubtractorGSOC();
  23. else if (algoName == String("LSBP"))
  24. algo = createBackgroundSubtractorLSBP();
  25. return algo;
  26. }
  27. int main()
  28. {
  29. // 视频路径
  30. String videoPath = "./video/vtest.avi";
  31. // 背景分割识别器序号
  32. int algo_index = 0;
  33. // 创建背景分割识别器
  34. Ptr<BackgroundSubtractor> bgfs = createBGSubtractorByName(algos[algo_index]);
  35. // 打开视频
  36. VideoCapture cap;
  37. cap.open(videoPath);
  38. // 如果视频没有打开
  39. if (!cap.isOpened())
  40. {
  41. std::cerr << "Cannot read video. Try moving video file to sample directory." << std::endl;
  42. return -1;
  43. }
  44. // 输入图像
  45. Mat frame;
  46. // 运动前景
  47. Mat fgmask;
  48. // 最后显示的图像
  49. Mat segm;
  50. // 延迟等待时间
  51. int delay = 30;
  52. // 获得运行环境CPU的核心数
  53. int nthreads = getNumberOfCPUs();
  54. // 设置线程数
  55. setNumThreads(nthreads);
  56. // 是否显示运动前景
  57. bool show_fgmask = false;
  58. // 平均执行时间
  59. float average_Time = 0.0;
  60. // 当前帧数
  61. int frame_num = 0;
  62. // 总执行时间
  63. float sum_Time = 0.0;
  64. for (;;)
  65. {
  66. // 提取帧
  67. cap >> frame;
  68. // 如果图片为空
  69. if (frame.empty())
  70. {
  71. // CAP_PROP_POS_FRAMES表示当前帧
  72. // 本句话表示将当前帧设定为第0帧
  73. cap.set(CAP_PROP_POS_FRAMES, 0);
  74. cap >> frame;
  75. }
  76. double time0 = static_cast<double>(getTickCount());
  77. // 背景建模
  78. bgfs->apply(frame, fgmask);
  79. time0 = ((double)getTickCount() - time0) / getTickFrequency();
  80. // 总执行时间
  81. sum_Time += time0;
  82. // 平均每帧执行时间
  83. average_Time = sum_Time / (frame_num + 1);
  84. if (show_fgmask)
  85. {
  86. segm = fgmask;
  87. }
  88. else
  89. {
  90. // 根据segm = alpha * frame + beta改变图片
  91. // 参数分别为,输出图像,输出图像格式,alpha值,beta值
  92. frame.convertTo(segm, CV_8U, 0.5);
  93. // 图像叠加
  94. // 参数分别为,输入图像/颜色1,输入图像/颜色2,输出图像,掩膜
  95. // 掩膜表示叠加范围
  96. add(frame, Scalar(100, 100, 0), segm, fgmask);
  97. }
  98. // 显示当前方法
  99. cv::putText(segm, algos[algo_index], Point(10, 30), FONT_HERSHEY_PLAIN, 2.0, Scalar(255, 0, 255), 2, LINE_AA);
  100. // 显示当前线程数
  101. cv::putText(segm, format("%d threads", nthreads), Point(10, 60), FONT_HERSHEY_PLAIN, 2.0, Scalar(255, 0, 255), 2, LINE_AA);
  102. // 显示当前每帧执行时间
  103. cv::putText(segm, format("averageTime %f s", average_Time), Point(10, 90), FONT_HERSHEY_PLAIN, 2.0, Scalar(255, 0, 255), 2, LINE_AA);
  104. cv::imshow("FG Segmentation", segm);
  105. int c = waitKey(delay);
  106. // 修改等待时间
  107. if (c == ' ')
  108. {
  109. delay = delay == 30 ? 1 : 30;
  110. }
  111. // 按C背景分割识别器
  112. if (c == 'c' || c == 'C')
  113. {
  114. algo_index++;
  115. if (algo_index > 6)
  116. algo_index = 0;
  117. bgfs = createBGSubtractorByName(algos[algo_index]);
  118. }
  119. // 设置线程数
  120. if (c == 'n' || c == 'N')
  121. {
  122. nthreads++;
  123. if (nthreads > 8)
  124. nthreads = 1;
  125. setNumThreads(nthreads);
  126. }
  127. // 是否显示背景
  128. if (c == 'm' || c == 'M')
  129. {
  130. show_fgmask = !show_fgmask;
  131. }
  132. // 退出
  133. if (c == 'q' || c == 'Q' || c == 27)
  134. {
  135. break;
  136. }
  137. // 当前帧数增加
  138. frame_num++;
  139. if (100 == frame_num)
  140. {
  141. String strSave = "out_" + algos[algo_index] + ".jpg";
  142. imwrite(strSave, segm);
  143. }
  144. }
  145. return 0;
  146. }

Python

  1. # -*- coding: utf-8 -*-
  2. """
  3. Created on Wed Aug 12 19:20:56 2020
  4. @author: luohenyueji
  5. """
  6. import cv2
  7. from time import *
  8. # TODO 背景减除算法集合
  9. ALGORITHMS_TO_EVALUATE = [
  10. (cv2.bgsegm.createBackgroundSubtractorGMG(20, 0.7), 'GMG'),
  11. (cv2.bgsegm.createBackgroundSubtractorCNT(), 'CNT'),
  12. (cv2.createBackgroundSubtractorKNN(), 'KNN'),
  13. (cv2.bgsegm.createBackgroundSubtractorMOG(), 'MOG'),
  14. (cv2.createBackgroundSubtractorMOG2(), 'MOG2'),
  15. (cv2.bgsegm.createBackgroundSubtractorGSOC(), 'GSOC'),
  16. (cv2.bgsegm.createBackgroundSubtractorLSBP(), 'LSBP'),
  17. ]
  18. # TODO 主函数
  19. def main():
  20. # 背景分割识别器序号
  21. algo_index = 0
  22. subtractor = ALGORITHMS_TO_EVALUATE[algo_index][0]
  23. videoPath = "./video/vtest.avi"
  24. show_fgmask = False
  25. # 获得运行环境CPU的核心数
  26. nthreads = cv2.getNumberOfCPUs()
  27. # 设置线程数
  28. cv2.setNumThreads(nthreads)
  29. # 读取视频
  30. capture = cv2.VideoCapture(videoPath)
  31. # 当前帧数
  32. frame_num = 0
  33. # 总执行时间
  34. sum_Time = 0.0
  35. while True:
  36. ret, frame = capture.read()
  37. if not ret:
  38. return
  39. begin_time = time()
  40. fgmask = subtractor.apply(frame)
  41. end_time = time()
  42. run_time = end_time - begin_time
  43. sum_Time = sum_Time + run_time
  44. # 平均执行时间
  45. average_Time = sum_Time / (frame_num + 1)
  46. if show_fgmask:
  47. segm = fgmask
  48. else:
  49. segm = (frame * 0.5).astype('uint8')
  50. cv2.add(frame, (100, 100, 0, 0), segm, fgmask)
  51. # 显示当前方法
  52. cv2.putText(segm, ALGORITHMS_TO_EVALUATE[algo_index][1], (10, 30), cv2.FONT_HERSHEY_PLAIN, 2.0, (255, 0, 255),
  53. 2,
  54. cv2.LINE_AA)
  55. # 显示当前线程数
  56. cv2.putText(segm, str(nthreads) + " threads", (10, 60), cv2.FONT_HERSHEY_PLAIN, 2.0, (255, 0, 255), 2,
  57. cv2.LINE_AA)
  58. # 显示当前每帧执行时间
  59. cv2.putText(segm, "averageTime {} s".format(average_Time), (10, 90), cv2.FONT_HERSHEY_PLAIN, 2.0,
  60. (255, 0, 255), 2, cv2.LINE_AA);
  61. cv2.imshow('some', segm)
  62. key = cv2.waitKey(1) & 0xFF
  63. frame_num = frame_num + 1
  64. # 按'q'健退出循环
  65. if key == ord('q'):
  66. break
  67. cv2.destroyAllWindows()
  68. if __name__ == '__main__':
  69. main()

2.2 评价

在i5六代CPU(太渣就不具体介绍),12G内存,VS2017 C++ Release平台下,各种方法处理速度如下表所示。

方法 单线程单帧处理平均时间/ms 四线程单帧处理平均时间/ms
GMG 38.6 31.3
CNT 4.6 2.9
KNN 19.8 9.3
MOG 16.3 15.6
MOG2 15.3 7.7
GSOC 66.3 49.4
LSBP 193.8 94.9

各个方法,个人评价如下:

  • GMG 初始建模帧会快速变化,导致全屏运动,对邻近运动目标检测效果一般,GMG需要自行设定参数(所以新的OpenCV标准库移除了GMG)总体效果一般。效果如图所示:
  • CNT 初始建模帧在一段时间持续变化导致全屏运动,运动目标过快可能会出现鬼影,低端设备速度很快,高端硬件速度和MOG2相近,总体效果不错。效果如图所示:
  • KNN 初始建模在一段时间持续变化导致全屏运动,运动目标都能较好检测出来,速度也还不错,总体效果不错。效果如图所示:
  • MOG 建模会丢失运动目标,速度不错,总体效果不错。效果如图所示:
  • MOG2 运动区域过大,容易出现细微变化区域,总体效果最好,MOG的升级版本,运动区域基本能检测出来,不过需要自行设定参数。效果如图所示:
  • GSOC 建模时间过短出现鬼影,随着建模时间越来越长,检测效果会变好,会逐渐消除鬼影,LSBP的升级版本,相对还行。效果如图所示:
  • LSBP 极易出现鬼影,建模次数越多,建模消耗时间有所减少,但是鬼影会偶尔出现。效果如图所示:

2.3 方法选择

  • 追求速度 CNT or MOG2 or KNN
    如果是低端设备或者并行任务多毫无疑问是CNT最好,高端设备还是MOG2更好,毕竟MOG2检测效果优于CNT,KNN也是不错的选择。

  • 追求质量 MOG2 or KNN or GSOC
    检测质量MOG2和KNN差不多,GSOC建模时间长会很不错,但是GSOC太慢了。如果不在意速度GSOC很好,其他还是MOG2和KNN。

  • 平衡质量和速度 MOG2 or KNN
    质量和速度均衡MOG2和KNN最不错,不然为什么MOG2和KNN放在标准库,其他在contrib库。MOG2需要调整参数,不过速度和质量优于KNN。如果图省心,不想调整参数,选KNN最好。

总的来说实际应用中,MOG2用的最多,KNN其次,CNT一般用于树莓派和多检测任务中。

3 参考

[OpenCV实战]43 使用OpenCV进行背景分割的更多相关文章

  1. [OpenCV实战]50 用OpenCV制作低成本立体相机

    本文主要讲述利用OpenCV制作低成本立体相机以及如何使用OpenCV创建3D视频,准确来说是模仿双目立体相机,我们通常说立体相机一般是指双目立体相机,就是带两个摄像头的那种(目就是指眼睛,双目就是两 ...

  2. [OpenCV实战]14 使用OpenCV实现单目标跟踪

    目录 1 背景 1.1 什么是目标跟踪 1.2 跟踪与检测 2 OpenCV的目标跟踪函数 2.1 函数调用 2.2 函数详解 2.3 综合评价 3 参考 在本教程中,我们将了解OpenCV 3中引入 ...

  3. [OpenCV实战]48 基于OpenCV实现图像质量评价

    本文主要介绍基于OpenCV contrib中的quality模块实现图像质量评价.图像质量评估Image Quality Analysis简称IQA,主要通过数学度量方法来评价图像质量的好坏. 本文 ...

  4. [OpenCV实战]47 基于OpenCV实现视觉显著性检测

    人类具有一种视觉注意机制,即当面对一个场景时,会选择性地忽略不感兴趣的区域,聚焦于感兴趣的区域.这些感兴趣的区域称为显著性区域.视觉显著性检测(Visual Saliency Detection,VS ...

  5. [OpenCV实战]44 使用OpenCV进行图像超分放大

    图像超分辨率(Image Super Resolution)是指从低分辨率图像或图像序列得到高分辨率图像.图像超分辨率是计算机视觉领域中一个非常重要的研究问题,广泛应用于医学图像分析.生物识别.视频监 ...

  6. [OpenCV实战]45 基于OpenCV实现图像哈希算法

    目前有许多算法来衡量两幅图像的相似性,本文主要介绍在工程领域最常用的图像相似性算法评价算法:图像哈希算法(img hash).图像哈希算法通过获取图像的哈希值并比较两幅图像的哈希值的汉明距离来衡量两幅 ...

  7. [OpenCV实战]46 在OpenCV下应用图像强度变换实现图像对比度均衡

    本文主要介绍基于图像强度变换算法来实现图像对比度均衡.通过图像对比度均衡能够抑制图像中的无效信息,使图像转换为更符合计算机或人处理分析的形式,以提高图像的视觉价值和使用价值.本文主要通过OpenCV ...

  8. [OpenCV实战]36 使用OpenCV在视频中实现简单背景估计

    目录 1 时间中值滤波 2 使用中值进行背景估计 3 帧差分 4 总结和代码 5 参考 许多计算机视觉应用中,硬件配置往往较低.在这种情况下,我们必须使用简单而有效的技术.在这篇文章中,我们将介绍一种 ...

  9. [OpenCV实战]25 使用OpenCV进行泊松克隆

    目录 1 Seamless Cloning实现 1.1 Seamless Cloning实例 1.2 正常克隆(NORMAL_CLONE)与混合克隆(MIXED_CLONE) 1.2.1 Normal ...

随机推荐

  1. CentOS 7.9 安装 ELK

    一.CentOS 7.9 安装 elasticsearch-7.8.1 地址 https://www.elastic.co https://www.elastic.co/cn/downloads/pa ...

  2. asp.net core web 解决方案多项目模板制作打包总结

    一.文件夹\项目结构 1.1.文件夹 net6.0:针对.net 6.0 项目模板 net6.0pack:针对net6.0打包 1.2.项目结构 Web\WebApi多项目.各层项目.单元测试项目 目 ...

  3. 16.MongoDB系列之分片管理

    1. 查看当前状态 1.1 查看配置信息 mongos> use config // 查看分片 mongos> db.shards.find() { "_id" : & ...

  4. JDBC连接SQL Server2008 完成增加、删除、查询、修改等基本信息基本格式及示例代码

    连接数据库的步骤: 1.注册驱动 (只做一次) 2.建立连接 3.创建执行SQL的语句.执行语句 4.处理执行结果 5.释放资源 1.建立连接的方法: Class.forName("com. ...

  5. Vue学习之--------列表渲染、v-for中key的原理、列表过滤的实现(2022/7/13)

    文章目录 1.基本列表 1.1 基本知识 1.2 代码实例 1.3 测试效果 2.key的原理 2.1基本知识 2.2 代码实例 2.3 测试效果 2.4 原理图解 3.列表过滤 3.1 代码实例 3 ...

  6. UVA10763

    菜鸡退役人来水黄了-- \(\sf{Solution}\) 搞不懂为什么要排序,这不是两个数组直接模拟的数数题吗. 读入后,对于每个学生,令他要去的学校以及他现在所在学校人数对应加一,再 check ...

  7. ubuntu 基本指令

    系统相关 df: disk free 用以显示系统上文件系统磁盘的使用情况 # 以M/G单位显示硬盘空间大小 df -h apt: advanced packaging tool 包管理工具 apt ...

  8. 记一次HTTPClient模拟登录获取Cookie的开发历程

    记一次HTTPClient模拟登录获取Cookie的开发历程 环境: ​ springboot : 2.7 ​ jdk: 1.8 ​ httpClient : 4.5.13 设计方案 ​ 通过新建一个 ...

  9. js高级之对象高级部分

    基于尚硅谷的尚硅谷JavaScript高级教程提供笔记撰写,加入一些个人理解 github源码 博客下载 对象的创建模式 Object构造函数模式 套路: 先创建空Object对象, 再动态添加属性/ ...

  10. Day16:冒泡排序详解

    冒泡排序 冒泡循环有两层循环,第一层控制循环轮数,第二层循环代表元素比较的次数. 利用冒泡排序获得升序或者降序的数组 //利用冒泡排序将一个数组进行降序排序 //思路: //冒泡排序是将相邻元素进行比 ...