[OpenCV实战]43 使用OpenCV进行背景分割
运动背景分割法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库的编译安装见:
2 代码与方法评估
2.1 代码
下述代码介绍了OpenCV_contrib的bgsegm模块中不同背景分割方法C++和Python的调用。对比了不同背景分割方法在示例视频下,单线程和多线程的效果。
代码和示例视频下载地址:
https://github.com/luohenyueji/OpenCV-Practical-Exercise
完整代码如下:
C++
#include <opencv2/opencv.hpp>
#include <opencv2/bgsegm.hpp>
#include <iostream>
using namespace cv;
using namespace cv::bgsegm;
const String algos[7] = { "GMG", "CNT", "KNN", "MOG", "MOG2", "GSOC", "LSBP" };
// 创建不同的背景分割识别器
static Ptr<BackgroundSubtractor> createBGSubtractorByName(const String& algoName)
{
Ptr<BackgroundSubtractor> algo;
if (algoName == String("GMG"))
algo = createBackgroundSubtractorGMG(20, 0.7);
else if (algoName == String("CNT"))
algo = createBackgroundSubtractorCNT();
else if (algoName == String("KNN"))
algo = createBackgroundSubtractorKNN();
else if (algoName == String("MOG"))
algo = createBackgroundSubtractorMOG();
else if (algoName == String("MOG2"))
algo = createBackgroundSubtractorMOG2();
else if (algoName == String("GSOC"))
algo = createBackgroundSubtractorGSOC();
else if (algoName == String("LSBP"))
algo = createBackgroundSubtractorLSBP();
return algo;
}
int main()
{
// 视频路径
String videoPath = "./video/vtest.avi";
// 背景分割识别器序号
int algo_index = 0;
// 创建背景分割识别器
Ptr<BackgroundSubtractor> bgfs = createBGSubtractorByName(algos[algo_index]);
// 打开视频
VideoCapture cap;
cap.open(videoPath);
// 如果视频没有打开
if (!cap.isOpened())
{
std::cerr << "Cannot read video. Try moving video file to sample directory." << std::endl;
return -1;
}
// 输入图像
Mat frame;
// 运动前景
Mat fgmask;
// 最后显示的图像
Mat segm;
// 延迟等待时间
int delay = 30;
// 获得运行环境CPU的核心数
int nthreads = getNumberOfCPUs();
// 设置线程数
setNumThreads(nthreads);
// 是否显示运动前景
bool show_fgmask = false;
// 平均执行时间
float average_Time = 0.0;
// 当前帧数
int frame_num = 0;
// 总执行时间
float sum_Time = 0.0;
for (;;)
{
// 提取帧
cap >> frame;
// 如果图片为空
if (frame.empty())
{
// CAP_PROP_POS_FRAMES表示当前帧
// 本句话表示将当前帧设定为第0帧
cap.set(CAP_PROP_POS_FRAMES, 0);
cap >> frame;
}
double time0 = static_cast<double>(getTickCount());
// 背景建模
bgfs->apply(frame, fgmask);
time0 = ((double)getTickCount() - time0) / getTickFrequency();
// 总执行时间
sum_Time += time0;
// 平均每帧执行时间
average_Time = sum_Time / (frame_num + 1);
if (show_fgmask)
{
segm = fgmask;
}
else
{
// 根据segm = alpha * frame + beta改变图片
// 参数分别为,输出图像,输出图像格式,alpha值,beta值
frame.convertTo(segm, CV_8U, 0.5);
// 图像叠加
// 参数分别为,输入图像/颜色1,输入图像/颜色2,输出图像,掩膜
// 掩膜表示叠加范围
add(frame, Scalar(100, 100, 0), segm, fgmask);
}
// 显示当前方法
cv::putText(segm, algos[algo_index], Point(10, 30), FONT_HERSHEY_PLAIN, 2.0, Scalar(255, 0, 255), 2, LINE_AA);
// 显示当前线程数
cv::putText(segm, format("%d threads", nthreads), Point(10, 60), FONT_HERSHEY_PLAIN, 2.0, Scalar(255, 0, 255), 2, LINE_AA);
// 显示当前每帧执行时间
cv::putText(segm, format("averageTime %f s", average_Time), Point(10, 90), FONT_HERSHEY_PLAIN, 2.0, Scalar(255, 0, 255), 2, LINE_AA);
cv::imshow("FG Segmentation", segm);
int c = waitKey(delay);
// 修改等待时间
if (c == ' ')
{
delay = delay == 30 ? 1 : 30;
}
// 按C背景分割识别器
if (c == 'c' || c == 'C')
{
algo_index++;
if (algo_index > 6)
algo_index = 0;
bgfs = createBGSubtractorByName(algos[algo_index]);
}
// 设置线程数
if (c == 'n' || c == 'N')
{
nthreads++;
if (nthreads > 8)
nthreads = 1;
setNumThreads(nthreads);
}
// 是否显示背景
if (c == 'm' || c == 'M')
{
show_fgmask = !show_fgmask;
}
// 退出
if (c == 'q' || c == 'Q' || c == 27)
{
break;
}
// 当前帧数增加
frame_num++;
if (100 == frame_num)
{
String strSave = "out_" + algos[algo_index] + ".jpg";
imwrite(strSave, segm);
}
}
return 0;
}
Python
# -*- coding: utf-8 -*-
"""
Created on Wed Aug 12 19:20:56 2020
@author: luohenyueji
"""
import cv2
from time import *
# TODO 背景减除算法集合
ALGORITHMS_TO_EVALUATE = [
(cv2.bgsegm.createBackgroundSubtractorGMG(20, 0.7), 'GMG'),
(cv2.bgsegm.createBackgroundSubtractorCNT(), 'CNT'),
(cv2.createBackgroundSubtractorKNN(), 'KNN'),
(cv2.bgsegm.createBackgroundSubtractorMOG(), 'MOG'),
(cv2.createBackgroundSubtractorMOG2(), 'MOG2'),
(cv2.bgsegm.createBackgroundSubtractorGSOC(), 'GSOC'),
(cv2.bgsegm.createBackgroundSubtractorLSBP(), 'LSBP'),
]
# TODO 主函数
def main():
# 背景分割识别器序号
algo_index = 0
subtractor = ALGORITHMS_TO_EVALUATE[algo_index][0]
videoPath = "./video/vtest.avi"
show_fgmask = False
# 获得运行环境CPU的核心数
nthreads = cv2.getNumberOfCPUs()
# 设置线程数
cv2.setNumThreads(nthreads)
# 读取视频
capture = cv2.VideoCapture(videoPath)
# 当前帧数
frame_num = 0
# 总执行时间
sum_Time = 0.0
while True:
ret, frame = capture.read()
if not ret:
return
begin_time = time()
fgmask = subtractor.apply(frame)
end_time = time()
run_time = end_time - begin_time
sum_Time = sum_Time + run_time
# 平均执行时间
average_Time = sum_Time / (frame_num + 1)
if show_fgmask:
segm = fgmask
else:
segm = (frame * 0.5).astype('uint8')
cv2.add(frame, (100, 100, 0, 0), segm, fgmask)
# 显示当前方法
cv2.putText(segm, ALGORITHMS_TO_EVALUATE[algo_index][1], (10, 30), cv2.FONT_HERSHEY_PLAIN, 2.0, (255, 0, 255),
2,
cv2.LINE_AA)
# 显示当前线程数
cv2.putText(segm, str(nthreads) + " threads", (10, 60), cv2.FONT_HERSHEY_PLAIN, 2.0, (255, 0, 255), 2,
cv2.LINE_AA)
# 显示当前每帧执行时间
cv2.putText(segm, "averageTime {} s".format(average_Time), (10, 90), cv2.FONT_HERSHEY_PLAIN, 2.0,
(255, 0, 255), 2, cv2.LINE_AA);
cv2.imshow('some', segm)
key = cv2.waitKey(1) & 0xFF
frame_num = frame_num + 1
# 按'q'健退出循环
if key == ord('q'):
break
cv2.destroyAllWindows()
if __name__ == '__main__':
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进行背景分割的更多相关文章
- [OpenCV实战]50 用OpenCV制作低成本立体相机
本文主要讲述利用OpenCV制作低成本立体相机以及如何使用OpenCV创建3D视频,准确来说是模仿双目立体相机,我们通常说立体相机一般是指双目立体相机,就是带两个摄像头的那种(目就是指眼睛,双目就是两 ...
- [OpenCV实战]14 使用OpenCV实现单目标跟踪
目录 1 背景 1.1 什么是目标跟踪 1.2 跟踪与检测 2 OpenCV的目标跟踪函数 2.1 函数调用 2.2 函数详解 2.3 综合评价 3 参考 在本教程中,我们将了解OpenCV 3中引入 ...
- [OpenCV实战]48 基于OpenCV实现图像质量评价
本文主要介绍基于OpenCV contrib中的quality模块实现图像质量评价.图像质量评估Image Quality Analysis简称IQA,主要通过数学度量方法来评价图像质量的好坏. 本文 ...
- [OpenCV实战]47 基于OpenCV实现视觉显著性检测
人类具有一种视觉注意机制,即当面对一个场景时,会选择性地忽略不感兴趣的区域,聚焦于感兴趣的区域.这些感兴趣的区域称为显著性区域.视觉显著性检测(Visual Saliency Detection,VS ...
- [OpenCV实战]44 使用OpenCV进行图像超分放大
图像超分辨率(Image Super Resolution)是指从低分辨率图像或图像序列得到高分辨率图像.图像超分辨率是计算机视觉领域中一个非常重要的研究问题,广泛应用于医学图像分析.生物识别.视频监 ...
- [OpenCV实战]45 基于OpenCV实现图像哈希算法
目前有许多算法来衡量两幅图像的相似性,本文主要介绍在工程领域最常用的图像相似性算法评价算法:图像哈希算法(img hash).图像哈希算法通过获取图像的哈希值并比较两幅图像的哈希值的汉明距离来衡量两幅 ...
- [OpenCV实战]46 在OpenCV下应用图像强度变换实现图像对比度均衡
本文主要介绍基于图像强度变换算法来实现图像对比度均衡.通过图像对比度均衡能够抑制图像中的无效信息,使图像转换为更符合计算机或人处理分析的形式,以提高图像的视觉价值和使用价值.本文主要通过OpenCV ...
- [OpenCV实战]36 使用OpenCV在视频中实现简单背景估计
目录 1 时间中值滤波 2 使用中值进行背景估计 3 帧差分 4 总结和代码 5 参考 许多计算机视觉应用中,硬件配置往往较低.在这种情况下,我们必须使用简单而有效的技术.在这篇文章中,我们将介绍一种 ...
- [OpenCV实战]25 使用OpenCV进行泊松克隆
目录 1 Seamless Cloning实现 1.1 Seamless Cloning实例 1.2 正常克隆(NORMAL_CLONE)与混合克隆(MIXED_CLONE) 1.2.1 Normal ...
随机推荐
- MatrixOne从入门到实践01——初识MatrixOne
初识MatrixOne 简介 MatrixOrigin 矩阵起源 是一家数据智能领域的创新企业,其愿景是成为数字世界的核心技术提供者. 物理世界的数字化和智能化无处不在.我们致力于建设开放的技术开源社 ...
- do-while循环的使用
一.循环结构的4个要素 ① 初始化条件 ② 循环条件 --->是boolean类型 ③ 循环体 ④ 迭代条件 二.do-while循环结构: ①do{ ③; ④;}while(②); 执行过程: ...
- ZJOI2007报表统计
题目链接 比较简单的一道平衡树题. 第三个操作可以直接用map完成(加进去一个数只会让答案变小,于是与它的前面后面一个数做差更新答案即可),只考虑前两个操作. ·维护区间内的最大最小值,以及区间相邻两 ...
- 使用doctest代码测试和Sphinx自动生成文档
python代码测试并自动生成文档 Tips:两大工具:doctest--单元测试.Sphinx--自动生成文档 1.doctest doctest是python自带的一个模块.doctest有两种使 ...
- 27.-Django发送邮件
一.邮件相关协议-SMTP SMTP全称是Simple Mail Transfer Protocol,即简单邮件传输协议(25端口号) 它是由一组从源地址到目的地址传输邮件的规范,通过它来控制邮件的中 ...
- Linux系统命令基础
Linux系统命令基础 前面咱们已经成功安装了Linux系统--centos7,那么现在奔向Linux命令行的世界. Linux命令格式 1.一般情况下,[参数]是可选的,一些情况下[文件或路径]也是 ...
- 将C#的bitmap格式转换为Halcon的图像格式
/// <summary> /// Bitmap转HObject灰度图 /// </summary> /// <param name="bmp"> ...
- Go语言核心36讲53
你好,我是郝林. 在2019年的春节来临之际,我恰好也更新完了专栏所有的配图和思考题答案.希望这些可以帮助到你,在新的一年中,祝你新年快乐,Go语言学习之路更加顺利. 基础概念篇 1. Go语言在多个 ...
- (C++) C++虚函数性能分析
class baseA { public: virtual float mulTwo(float a, float b) = 0; virtual ~baseA() = default; }; cla ...
- Spring校验器实例
@size (min=3, max=20,message="用户名长度只能在3-20之间") @size (min=6, max=20,message="密码长度只能在6 ...