• 2020-04-14 19:15:14

今天朋友问我要一个车上充满点点的图片，然后我第一时间想到了光流法，然后想到了之前总结的运动物体检测的几个方法，还在有道云笔记里面，所以打算搬迁过来。

## 帧间差分法

定义：利用相邻的两帧或者三帧图像，利用像素之间的差异性，判断是否有运动目标

基本步骤：相邻帧相减－－－阈值处理－－－去除噪声－－－膨胀联通－－－查找轮廓－－－绘制外接矩形

参考方法：https://www.cnblogs.com/little-monkey/p/7637130.html
#include "opencv2/opencv.hpp"
#include<iostream>
using namespace std;
using namespace cv;
​
int CarNum = 0;
string intToString(int number)  //int类型转为string类型
{
stringstream ss;
ss << number;
return ss.str();
}
​
Mat MoveDetect(Mat frame1, Mat frame2) {
Mat result = frame2.clone();
Mat gray1, gray2;
cvtColor(frame1, gray1, COLOR_BGR2GRAY);
cvtColor(frame2, gray2, COLOR_BGR2GRAY);
​
Mat diff;
absdiff(gray1, gray2, diff);
//imshow("absdiss", diff);
threshold(diff, diff, 50, 255, THRESH_BINARY);
imshow("threshold", diff);
​
medianBlur(diff, diff, 5);
imshow("medianBlur", diff);
​
Mat element = getStructuringElement(MORPH_RECT, Size(3, 3));
Mat element2 = getStructuringElement(MORPH_RECT, Size(50, 50));
erode(diff, diff, element);
dilate(diff, diff, element2);
imshow("dilate", diff);
​
vector<vector<Point>> contours;
vector<Vec4i> hierarcy;
findContours(diff, contours, hierarcy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE, Point(0, 0));//查找轮廓
vector<vector<Point>>contours_poly(contours.size());
vector<Rect> boundRect(contours.size()); //定义外接矩形集合
//drawContours(img2, contours, -1, Scalar(0, 0, 255), 1, 8);  //绘制轮廓
int x0 = 0, y0 = 0, w0 = 0, h0 = 0;
for (int i = 0; i<contours.size(); i++)
{
//对图像轮廓点进行多边形拟合：轮廓点组成的点集，输出的多边形点集，精度（即两个轮廓点之间的距离），输出多边形是否封闭
approxPolyDP(Mat(contours[i]), contours_poly[i], 3, true);
boundRect[i] = boundingRect(Mat(contours_poly[i]));
if (boundRect[i].width>55 && boundRect[i].width<180 && boundRect[i].height>55 && boundRect[i].height<180) {//轮廓筛选
x0 = boundRect[i].x;
y0 = boundRect[i].y;
w0 = boundRect[i].width;
h0 = boundRect[i].height;
​
rectangle(result, Point(x0, y0), Point(x0 + w0, y0 + h0), Scalar(0, 255, 0), 2, 8, 0);
//经过这条线（区间），车辆数量+1
if ((y0 + h0 / 2 + 1) >= 138 && (y0 + h0 / 2 - 1) <= 142) {
CarNum++;
}
}
line(result, Point(0, 140), Point(568, 140), Scalar(0, 0, 255), 1, 8);//画红线
Point org(0, 35);
putText(result, "CarNum=" + intToString(CarNum), org, FONT_HERSHEY_SIMPLEX, 0.8f, Scalar(0, 255, 0), 2);
}
return result;
}
​
int main() {
VideoCapture cap("out3.avi");
if (!cap.isOpened()) //检查打开是否成功
return -1;
Mat frame;
Mat tmp;
Mat result;
int count = 0;
while (1) {
cap >> frame;
if (frame.empty())//检查视频是否结束
break;
else {
resize(frame,frame,Size(640,480));
count++;
if (count == 1)
result = MoveDetect(frame, frame);
else result = MoveDetect(tmp, frame);
imshow("video", frame);
imshow("result", result);
tmp = frame.clone();
if (waitKey(20) == 27)
break;
}
}
cap.release();
return 0;
}

## 背景减弱法原理

定义：用原图像减去背景模型，剩下的就是前景图像，即运动目标

基本步骤：原图－－背景－－阈值处理－－－去除噪声（腐蚀滤波）－－－膨胀连通－－－查找轮廓－－－外接矩形

代码实现：BackgroundSubtractor一共给我们提供了三种具体方法，分别是BackgroundSubtractorMOG, BackgroundSubtractorMOG2和BackgroundSubtractorGMG (这是基于基于3.1.0)

这三种方法的具体区别及使用方法可以参考这篇官方文档,但是我在官网中看到4.1.0中，只有BackgroundSubtractorKNNBackgroundSubtractorMOG2这两种方法。

//参考链接：https://blog.csdn.net/qq_32925781/article/details/52878465
// 方法：BackgroundSubtractorMOG2
#include "opencv2/imgcodecs.hpp"
#include "opencv2/imgproc.hpp"
#include "opencv2/videoio.hpp"
#include <opencv2/highgui.hpp>
#include <opencv2/video.hpp>
#include <stdio.h>
#include <iostream>
#include <sstream>
​
using namespace cv;
using namespace std;
​
Mat frame; //当前帧
Mat segm;      //frame的副本
​
vector<vector<Point> > contours;
vector<Vec4i> hierarchy;
Ptr<BackgroundSubtractor> pMOG2; //MOG2 Background subtractor
​
//处理输入视频函数定义
void processVideo();
​
int main()
{
//namedWindow("Original Frame");
//namedWindow("After MOG2");
//create Background Subtractor objects
pMOG2 = createBackgroundSubtractorMOG2();
​
processVideo();
​
destroyAllWindows();
return 0;
}
​
​
void processVideo() {
​
VideoCapture capture(1); //参数为0，默认从摄像头读取视频
​
if(!capture.isOpened()){
cout << "Unable to open the camera! " << endl;
//EXIT_FAILURE 可以作为exit()的参数来使用，表示没有成功地执行一个程序,其值为1
exit(EXIT_FAILURE);
}
​
while( true ){
​
cout << "Unable to read next frame." << endl;
exit(0);
}
​
//对画面进行一定的缩放，方便处理
double scale = 1.3;         //缩放比例
Mat smallImg(frame.rows / scale,frame.cols / scale,CV_8SC1);
resize(frame, frame, smallImg.size(),0,0,INTER_LINEAR);
​
​
frame.copyTo(segm);             //建立一个当前frame的副本
RETR_TREE, CHAIN_APPROX_SIMPLE,Point(0,0)); //检测轮廓
​
vector <vector<Point> > contours_poly( contours.size());
vector <Point2f> center( contours.size());
for( int i = 0; i < contours.size(); i++){
//findContours后的轮廓信息contours可能过于复杂不平滑，
//可以用approxPolyDP函数对该多边形曲线做适当近似
approxPolyDP( Mat(contours[i]), contours_poly[i], 3, true);
//得到轮廓的外包络圆
}
//对所得到的轮廓进行一定的筛选
for(int i = 0; i < contours.size(); i++ ){
if (contourArea(contours[i]) > 500){
circle(segm, center[i], (int)radius[i], Scalar(100, 100, 0), 2, 8, 0);
break;
}
}
​
//得到当前是第几帧
stringstream ss;
//        rectangle(frame, cv::Point(10, 2), cv::Point(100,20),
//                  cv::Scalar(255,255,255), -1);
ss << capture.get(CAP_PROP_POS_FRAMES);
FONT_HERSHEY_SIMPLEX, 0.5 , cv::Scalar(0,0,0));
​
//显示
imshow("frame", frame);
imshow("Segm", segm);
​
int key;
key = waitKey(5);
if (key == 'q' || key == 'Q' || key == 27)
break;
}
​
capture.release();
}

## 光流场法

定义:一般而言，光流是由于场景中前景目标本身的移动、相机的运动，或者两者的共同运动所产生的。

原理：

/*稀疏光流阀*/
#include <iostream>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/videoio.hpp>
#include <opencv2/video.hpp>
using namespace cv;
using namespace std;
int main(int argc, char **argv)
{
​
if (!capture.isOpened()){
//error in opening the video input
cerr << "Unable to open file!" << endl;
return 0;
}
// Create some random colors
vector<Scalar> colors;
RNG rng;
for(int i = 0; i < 100; i++)
{
int r = rng.uniform(0, 256);
int g = rng.uniform(0, 256);
int b = rng.uniform(0, 256);
colors.push_back(Scalar(r,g,b));
}
Mat old_frame, old_gray;
vector<Point2f> p0, p1;
// Take first frame and find corners in it
capture >> old_frame;
cvtColor(old_frame, old_gray, COLOR_BGR2GRAY);
goodFeaturesToTrack(old_gray, p0, 100, 0.3, 7, Mat(), 7, false, 0.04);
// Create a mask image for drawing purposes
while(true){
Mat frame, frame_gray;
capture >> frame;
if (frame.empty())
break;
cvtColor(frame, frame_gray, COLOR_BGR2GRAY);
// 计算光流点
vector<uchar> status;
vector<float> err;
//设置迭代终止条件
TermCriteria criteria = TermCriteria((TermCriteria::COUNT) + (TermCriteria::EPS), 10, 0.03);
​
calcOpticalFlowPyrLK(old_gray, frame_gray, p0, p1, status, err, Size(15,15), 2, criteria);
​
vector<Point2f> good_new;
for(uint i = 0; i < p0.size(); i++)
{
// Select good points
if(status[i] == 1) {
good_new.push_back(p1[i]);
// draw the tracks
//                circle(frame, p1[i], 5, Scalar(0,0,255), -1);
}
}
​
Mat img;
imshow("Frame", img);
int keyboard = waitKey(30);
if (keyboard == 'q' || keyboard == 27)
break;
// Now update the previous frame and previous points
old_gray = frame_gray.clone();
p0 = good_new;
}
}


## 写视频

写视频
//实现写视频功能
#include<opencv2/opencv.hpp>
#include<iostream>
using namespace cv;
using namespace std;
​
int main()
{
//    'M', 'J', 'P', 'G'　　　　'X','V','I','D'
//Size要和图片尺寸保持一致
VideoWriter writer("blue_red.avi",cv::VideoWriter::fourcc('X','V','I','D'),8,Size(1280,1024),true);
char filename[50];
Mat frame;
for (int i = 1; i < 243; i++)
{
sprintf(filename,"//home/demon/MVViewer/6mm-two/%d.bmp",i);
if(frame.empty())   break;
writer<<frame;
​
}
cout<<"write end!"<<endl;
destroyAllWindows();
return 0;
}


更多相关内容
• 主要为大家详细介绍了基于OpenCv的运动物体检测算法，具有一定的参考价值，感兴趣的小伙伴们可以参考一下
• 运动物品检测，用C#编写的，可以检测运动物体
• 基于跟踪的运动物体检测
• opencv 更新背景 检测运动物体
• 实现目标检测的代码，有例程视频，可以运行
• 视频序列中多运动物体检测与跟踪方法，龙浩，张磊，首先对图像预处理技术进行了研究，采用了一种改进的中值滤波图像平滑方法，并进行了实验对比和分析。其次，针对三种常用的运动目
• 通过稳健的主成分分析进行运动物体检测
• 为了提升动态场景中视觉里程计的稳健性和精确度，提出一种基于运动物体检测的立体视觉里程计算法。首先，建立考虑相机位姿的场景流计算模型，用于表示物体的运动矢量。其次，提出构造虚拟地图点的方法，一方面结合...
• 开发的使用usb摄像头进行运动物体检测的工具。当摄像头拍摄到运动物体后，会进行图片保存。
• 基于机器学习的运动物体检测算法
• 今天学习运动物体检测 一：帧差法 捕获摄像头的运动的手 import cv2 import numpy as np # 如果我们想捕获一些运动的物体，每一帧图像中，不动的部分称之为背景，运动的物体称之为前景 # 假如我们的视频捕捉窗口是...

今天学习运动物体检测
一：帧差法
捕获摄像头的运动的手

import cv2
import numpy as np

# 如果我们想捕获一些运动的物体，每一帧图像中，不动的部分称之为背景，运动的物体称之为前景
# 假如我们的视频捕捉窗口是不动的，比如摄像头放着不动，保证了背景是基本不发生变化的，但是我们怎么捕获前景和背景啊？
# 第一部分: 帧差法
# 通过前后两帧的差值来捕捉运动的物体（一般用时间t的帧减去时间t-1的帧），超过某个阈值，则判断是前景，否则是背景
# 这个方法很简单，但是会带来巨大的噪声（微微震动，零星点）和空洞（运动物体非边缘部分也被判断成了背景）

cap = cv2.VideoCapture(0)  # 其参数0表示第一个摄像头，一般就是笔记本的内建摄像头。
# cap = cv2.VideoCapture('images/kk 2022-01-23 18-21-21.mp4')  # 来自vedio视频的

# 获取第一帧
frame_prev = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)  # 将彩色图像转成灰度图

kernel1 = np.ones((5, 5), np.uint8)  # 为了开运算使用
while (1):
# 获取每一帧
if frame is None:
print("camera is over...")
break

frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)  # 将彩色图像转成灰度图
diff = frame - frame_prev  # 肯定有一些是负数，全是灰度值的相减

diff_abs = cv2.convertScaleAbs(diff)  # 取绝对值，保留我们的差异值

_, thresh1 = cv2.threshold(diff_abs, 100, 255, cv2.THRESH_BINARY)  # 二值化处理

MORPH_OPEN_1 = cv2.morphologyEx(thresh1, cv2.MORPH_OPEN, kernel1)  # 开运算，去除噪声和毛刺
# erosion_it2r_1 = cv2.dilate(MORPH_OPEN_1, kernel1, iterations=2)  # 膨胀操作

# cv2.imshow("capture", thresh1)  # 展示该图像
cv2.imshow("capture", MORPH_OPEN_1)  # 展示该图像

frame_prev = frame  # 更新前一帧

# 进行等待或者退出判断
if cv2.waitKey(1) & 0xFF == 27:
break
cap.release()
cv2.destroyAllWindows()



捕捉视频的帧，捕获坤坤的打篮球和跳舞

import cv2
import numpy as np

# 如果我们想捕获一些运动的物体，每一帧图像中，不动的部分称之为背景，运动的物体称之为前景
# 假如我们的视频捕捉窗口是不动的，比如摄像头放着不动，保证了背景是基本不发生变化的，但是我们怎么捕获前景和背景啊？
# 第一部分: 帧差法
# 通过前后两帧的差值来捕捉运动的物体（一般用时间t的帧减去时间t-1的帧），超过某个阈值，则判断是前景，否则是背景
# 这个方法很简单，但是会带来巨大的噪声（微微震动，零星点）和空洞（运动物体非边缘部分也被判断成了背景）

cap = cv2.VideoCapture('images/kk 2022-01-23 18-21-21.mp4')  # 来自vedio视频的

# 获取第一帧
frame_prev = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)  # 将彩色图像转成灰度图

# kernel1 = np.ones((5, 5), np.uint8)  # 为了开运算使用
while (1):
# 获取每一帧
if frame is None:
print("camera is over...")
break

frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)  # 将彩色图像转成灰度图
diff = frame - frame_prev  # 肯定有一些是负数，全是灰度值的相减

diff_abs = cv2.convertScaleAbs(diff)  # 取绝对值，保留我们的差异值

_, thresh1 = cv2.threshold(diff_abs, 100, 255, cv2.THRESH_BINARY)  # 二值化处理

# MORPH_OPEN_1 = cv2.morphologyEx(thresh1, cv2.MORPH_OPEN, kernel1)  # 开运算，去除噪声和毛刺
# erosion_it2r_1 = cv2.dilate(MORPH_OPEN_1, kernel1, iterations=2)  # 膨胀操作

cv2.imshow("capture", diff_abs)  # 展示该图像
# cv2.imshow("capture", thresh1)  # 展示该图像
# cv2.imshow("capture", MORPH_OPEN_1)  # 展示该图像

frame_prev = frame  # 更新前一帧

# 进行等待或者退出判断
if cv2.waitKey(24) & 0xFF == 27:
break
cap.release()
cv2.destroyAllWindows()


效果如下：

总体而言，帧差法的效果真的是不忍直视。

二：高斯混合模型（GMM）
在进行前景检测前，先进行背景的学习和训练，对图像的每一个背景采用一个GMM进行模拟，每个背景的混合高斯模型是可以训练的，是自适应的。在测试阶段，对新来的像素进行GMM检测，如果像素值匹配某个高斯模型（进行概率值计算，看看和哪个高斯模型接近，或者看看和均值的偏离程度），则认为是背景，否则认为是前景（剧烈变化的像素，属于异常而原理GMM模型）。由于整个过程中GMM模型在不断的更新学习，多以对动态背景有一定的鲁棒性。

我们的视频中的每一帧的像素点不可能是一成不变的（理想很丰满），总会有空气密度导致的变化，吹一吹风，或者摄像头的微微抖动。但是正常情况下，我们都认为背景中的像素点都是满足一个高斯分布，在一定的概率下抖动是正常的。

我们的背景实际上也应当是多个高斯分布的混合分布，且每个高斯模型也是可以带权重的。因为背景有蓝天，建筑，树木花草，马路等。这些都是一些分布。

至于GMM和EM算法的内容和关系，我们在之前学习过，这里就不再讲述了。EM算法是求解GMM的一个方法。

运用在图像中的话，是怎么做的呢？步骤如下：
1：首先初始化每个高斯模型矩阵参数，初始的方差都是假设的（比如设置为5）。
2：先给T帧图像进行训练GMM，来了第一个像素后把他当成第一个高斯分布，比如作为均值。
3：后面再来一个像素，和当前的高斯分布作对比，如果该像素值和与当前的高斯模型的均值的差值在3倍的方差以内，就认为是属于该高斯分布，并对高斯分布参数进行更新。
4：如果该像素不满足该高斯分布。则用这个新的像素建立一个新的高斯分布（用它做均值，假设一个方差（比如设置为5））。但是一个像素位置不要太多个高斯分布，太多了会造成计算量巨大。3~5个基本够了。

GMM测试步骤：
对于新来的像素值，和GMM的的多个高斯分布的均值分别作比较，其差值在2倍的方差之内，则认为是背景，否则是前景。前景像素点设置为255，背景像素点设置为0，行成了一个二值图像。

import cv2
import numpy as np

cap = cv2.VideoCapture('images/kk 2022-01-23 18-21-21.mp4')  # 来自vedio视频的

kernel1 = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
mog = cv2.createBackgroundSubtractorMOG2()  # 创建混合高斯模型来用于北京建模

while (1):
# 获取每一帧
if frame is None:
print("camera is over...")
break

MORPH_OPEN_1 = cv2.morphologyEx(fmask, cv2.MORPH_OPEN, kernel1)  # 开运算，去除噪声和毛刺

contours, _ = cv2.findContours(fmask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)  # 只检测外边框

for cont in contours:
# 计算各个轮廓的面积
len = cv2.arcLength(cont, True)
if len > 300:  # 去除一些小的噪声点
# 找到一个轮廓
x,y,w,h = cv2.boundingRect(cont)
# 画出这个矩形
cv2.rectangle(frame, (x,y), (x+w, y+h), color=(0,255,0), thickness=3)

# 画出所有的轮廓
cv2.imshow('frame', frame)

# 进行等待或者退出判断
if cv2.waitKey(24) & 0xFF == 27:
break

cap.release()
cv2.destroyAllWindows()


效果如下：

三：KNN
也是根据每个像素点的历史信息进行统计额比较，来判断新来的像素值是不是背景。具体的原理后面补充。

import cv2
import numpy as np

cap = cv2.VideoCapture('images/kk 2022-01-23 18-21-21.mp4')  # 来自vedio视频的

kernel1 = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
knn = cv2.createBackgroundSubtractorKNN()  # 创建KNN模型

while (1):
# 获取每一帧
if frame is None:
print("camera is over...")
break

MORPH_OPEN_1 = cv2.morphologyEx(fmask, cv2.MORPH_OPEN, kernel1)  # 开运算，去除噪声和毛刺

contours, _ = cv2.findContours(fmask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)  # 只检测外边框

for cont in contours:
# 计算各个轮廓的面积
len = cv2.arcLength(cont, True)
if len > 200:  # 去除一些小的噪声点
# 找到一个轮廓
x,y,w,h = cv2.boundingRect(cont)
# 画出这个矩形
cv2.rectangle(frame, (x,y), (x+w, y+h), color=(0,255,0), thickness=3)

# 画出所有的轮廓
cv2.imshow('frame', frame)

# 进行等待或者退出判断
if cv2.waitKey(24) & 0xFF == 27:
break

cap.release()
cv2.destroyAllWindows()


效果如下：

展开全文
• 运动物体检测_C#运动_lungsa3n_运动物体检测_源码
• opencv的运动物体检测并框出，只要环境对的话，测试就没有问题。
• 运动目标的检测与跟踪已在众多的领域得到了广泛的应用，但是由于嵌入式处理器自身的速度限制，此类应用主要集中在PC机上，相对来说，成本较高而且灵活性和移动性能不够好。将此算法应用到嵌入式系统上能有效地降低...
• 基于openCV+python 的一系列检测操作，较为基础
• 通过深入研究国内外视频图像运动目标的跟踪技术现状，基于目前对视频图像中运动物体进行检测与跟踪设备的便携性差、耗电量高等缺点，本系统利用ARM11平台搭载Linux系统实现相关应用的方法，完成了一套较完整的小型化...
• 基于ARM11的视频图像中运动物体检测跟踪系统
• 运动物体检测RecordCode效果 Record Code 效果

## Record

1、运动物体检测常用的方法：背景差法、帧差法、光流法；
2、背景差法使用原图像减去背景图像，得到前景图像，也就是运动目标；
3、帧差法根据相邻两帧或三帧图像，利用像素间的差异性，判断是否有运动目标；
4、背景差法与帧差法都进行图像减法，最常使用AbsDidd()函数；
5、背景差法基本步骤：

6、帧差法基本步骤：

7、Else:

## Code

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Drawing;
using Emgu.CV;
using Emgu.CV.Util;
using Emgu.CV.CvEnum;
using Emgu.CV.Structure;
using Emgu.Util;

namespace lesson35
{
class Program
{
static void Main(string[] args)
{
图片背景差法检测运动物体
//CvInvoke.Imshow("bg", bgImg);
//CvInvoke.Imshow("fg", fgImg);
转换灰度图
//Mat gray1 = new Mat();
//Mat gray2 = new Mat();
//CvInvoke.CvtColor(bgImg, gray1, ColorConversion.Bgr2Gray);
//CvInvoke.CvtColor(fgImg, gray2, ColorConversion.Bgr2Gray);
图片做差
//Mat diff = new Mat();
//CvInvoke.AbsDiff(gray2, gray1, diff);
//CvInvoke.Imshow("diff", diff);
二值化
//CvInvoke.Threshold(diff, diff, 50, 255, ThresholdType.Binary);
//CvInvoke.Imshow("threshold", diff);
中值滤波或腐蚀去除噪点(中值滤波效果更好)
CvInvoke.MedianBlur(diff, diff, 3);
CvInvoke.Imshow("medianblur", diff);
//Mat element = CvInvoke.GetStructuringElement(ElementShape.Rectangle, new Size(3, 3), new Point(-1, -1));
//CvInvoke.Erode(diff, diff, element, new Point(-1, -1), 1, BorderType.Default, new MCvScalar());
//CvInvoke.Imshow("erode", diff);
膨胀连通区域
//Mat element2 = CvInvoke.GetStructuringElement(ElementShape.Rectangle, new Size(11, 11), new Point(-1, -1));
//CvInvoke.Dilate(diff, diff, element2, new Point(-1, -1), 1, BorderType.Default, new MCvScalar());
//CvInvoke.Imshow("dilate", diff);
绘制最小外接矩形
//VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint();
//CvInvoke.FindContours(diff, contours, null, RetrType.External, ChainApproxMethod.ChainApproxNone);
//for(int i = 0; i < contours.Size; i++)
//{
//    Rectangle rect = CvInvoke.BoundingRectangle(contours[i]);
//    CvInvoke.Rectangle(fgImg, rect, new MCvScalar(0, 0, 255), 2);
//}
//CvInvoke.Imshow("result", fgImg);
//CvInvoke.WaitKey(0);

///视频背景差法检测运动物体
//VideoCapture cap = new VideoCapture("ball.avi");
//if (!cap.IsOpened)
//{
//    Console.WriteLine("open the video failed..");
//    return;
//}
//int count = 0;
//Mat bgImg = new Mat();
//Mat frame = new Mat();
//while (true)
//{
//    if (frame.IsEmpty)
//    {
//        Console.WriteLine("frame is empty...");
//        break;
//    }
//    count++;
//    if (1 == count)
//    {
//        bgImg = frame.Clone();  //取第一帧为背景
//    }
//    CvInvoke.Imshow("video", frame);
//    Mat result = MoveDetect(bgImg, frame);
//    CvInvoke.Imshow("move", result);
//    if (CvInvoke.WaitKey(50) == 27)
//    {
//        break;
//    }
//}

///图片帧差法检测运动物体
//CvInvoke.Imshow("bg", bgImg);
//CvInvoke.Imshow("fg", fgImg);
转换为灰度图
//Mat gray = new Mat();
//Mat gray2 = new Mat();
//CvInvoke.CvtColor(bgImg, gray, ColorConversion.Bgr2Gray);
//CvInvoke.CvtColor(fgImg, gray2, ColorConversion.Bgr2Gray);
做差
//Mat diff = new Mat();
//CvInvoke.AbsDiff(gray2, gray, diff);
//CvInvoke.Imshow("diff", diff);
二值化
//CvInvoke.Threshold(diff, diff, 45, 255, ThresholdType.Binary);
//CvInvoke.Imshow("threshold", diff);
//Mat element = CvInvoke.GetStructuringElement(ElementShape.Rectangle, new Size(1, 1), new Point(-1, -1));
//CvInvoke.Erode(diff, diff, element, new Point(-1, -1), 1, BorderType.Default, new MCvScalar());
//CvInvoke.Imshow("erode", diff);
膨胀连通区域
//Mat element2 = CvInvoke.GetStructuringElement(ElementShape.Rectangle, new Size(11, 11), new Point(-1, -1));
//CvInvoke.Dilate(diff, diff, element2, new Point(-1, -1), 1, BorderType.Default, new MCvScalar());
//CvInvoke.Imshow("dilate", diff);
绘制最小外接矩形
//VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint();
//CvInvoke.FindContours(diff, contours, null, RetrType.External, ChainApproxMethod.ChainApproxNone);
//for (int i = 0; i < contours.Size; i++)
//{
//    Rectangle rect = CvInvoke.BoundingRectangle(contours[i]);
//    CvInvoke.Rectangle(fgImg, rect, new MCvScalar(0, 0, 255), 2);
//}
//CvInvoke.Imshow("result", fgImg);
//CvInvoke.WaitKey(0);

///视频帧差法检测运动物体
VideoCapture cap = new VideoCapture("man.avi");
if(!cap.IsOpened)
{
Console.WriteLine("Open video failed..");
return;
}
int count = 0;
Mat bgImg = new Mat();
Mat frame = new Mat();
while(true)
{
if(frame.IsEmpty)
{
Console.WriteLine("frame is empty..");
break;
}
count++;
if (count == 1)
bgImg = frame.Clone();
Mat result = MoveDetect2(bgImg, frame);
CvInvoke.Imshow("move", result);
bgImg = frame.Clone();	//更新前一帧
if(CvInvoke.WaitKey(30)==27)
{
break;
}

}

}
static Mat MoveDetect2(Mat bgImg,Mat fgImg)
{
Mat result = fgImg.Clone();
Mat gray = new Mat();
Mat gray2 = new Mat();
CvInvoke.CvtColor(bgImg, gray, ColorConversion.Bgr2Gray);
CvInvoke.CvtColor(fgImg, gray2, ColorConversion.Bgr2Gray);
//做差
Mat diff = new Mat();
CvInvoke.AbsDiff(gray2, gray, diff);
CvInvoke.Imshow("diff", diff);
//二值化
CvInvoke.Threshold(diff, diff, 45, 255, ThresholdType.Binary);
CvInvoke.Imshow("threshold", diff);
//中值滤波
CvInvoke.MedianBlur(diff, diff, 5);
CvInvoke.Imshow("median blur", diff);
//膨胀
Mat element = CvInvoke.GetStructuringElement(ElementShape.Rectangle, new Size(9, 9), new Point(-1, -1));
CvInvoke.Dilate(diff, diff, element, new Point(-1, -1), 1, BorderType.Default, new MCvScalar());
CvInvoke.Imshow("dilate", diff);
//绘制轮廓矩形
VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint();
CvInvoke.FindContours(diff, contours, null, RetrType.External, ChainApproxMethod.ChainApproxNone);
for (int i = 0; i < contours.Size; i++)
{
Rectangle rect = CvInvoke.BoundingRectangle(contours[i]);
CvInvoke.Rectangle(result, rect, new MCvScalar(0, 0, 255), 2);
}
return result;
}

static Mat MoveDetect(Mat bgImg, Mat fgImg)
{
Mat result = fgImg.Clone();
Mat gray = new Mat();
Mat gray2 = new Mat();
CvInvoke.CvtColor(bgImg, gray, ColorConversion.Bgr2Gray);
CvInvoke.CvtColor(fgImg, gray2, ColorConversion.Bgr2Gray);
//做差
Mat diff = new Mat();
CvInvoke.AbsDiff(gray2, gray, diff);
CvInvoke.Imshow("diff", diff);
//二值化
CvInvoke.Threshold(diff, diff, 10, 255, ThresholdType.Binary);
CvInvoke.Imshow("threshold", diff);
//中值滤波
CvInvoke.MedianBlur(diff, diff, 5);
CvInvoke.Imshow("median blur", diff);
//膨胀
Mat element = CvInvoke.GetStructuringElement(ElementShape.Rectangle, new Size(9, 9), new Point(-1, -1));
CvInvoke.Dilate(diff, diff, element, new Point(-1, -1), 1, BorderType.Default, new MCvScalar());
CvInvoke.Imshow("dilate", diff);
//绘制轮廓矩形
VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint();
CvInvoke.FindContours(diff, contours, null, RetrType.External, ChainApproxMethod.ChainApproxNone);
for(int i = 0; i < contours.Size; i++)
{
Rectangle rect = CvInvoke.BoundingRectangle(contours[i]);
CvInvoke.Rectangle(result, rect, new MCvScalar(0, 0, 255), 2);
}

return result;

}

}
}


## 效果

#### 4. 视频帧差法检测运动物体

展开全文
• 内含代码可以检测运动物体，采用高斯混合模型算法提取前景
• matlab解压代码广域运动图像中的运动物体检测（matlab 版） WAMI 图像的运动物体检测（车辆） 此代码是我们的论文“使用卷积神经网络 (CNNs) 检测和跟踪广域运动图像 (WAMI) 中的小运动对象”的实现，该论文已在 ...
• Python帧差法运动物体检测效果
• 适用于不变背景上的移动物体检测，包含一个测试视频集合和带可视化的matlab代码，包含最终检测到移动物体的标注
• ## 运动物体检测以及追踪

万次阅读 多人点赞 2018-02-23 23:34:48
概括：运动物体检测是应用差分法实现，运动物体追踪是利用基于时间序列预测模型实现。 运动物体检测 常用的检测方法为背景减除法和帧差法，这两种方法原理基本上都是图片相减。两种方法各有优缺点： 帧差法适用...

概括：运动物体检测是应用差分法实现，运动物体追踪是利用基于时间序列预测模型实现。
##运动物体检测
常用的检测方法为背景减除法和帧差法，这两种方法原理基本上都是图片相减。两种方法各有优缺点：

帧差法适用于更多场景，如：摄像头移动以及多目标运动场景，缺点就是检测的人物之间容易出现空洞。
背景减除法，适用场景局限，只适用于第一帧是背景图的视频，但检测人物没有空洞。
以上这两种方法均不能很好的处理光照过强以及阴影的运动物体视频。

物体检测方法还有许多，有基于图像相减的三帧差法，还有对于电脑性能要求较高的光流法。

#### 效果

背景减除法：（人物没有出现空洞现象，但对于光照过强区域效果不好）

帧差法：(人物出现空洞，对下一步处理造成影响)

## 运动物体追踪

之前一直对每一帧检测的运动物体进行标志就以为达到了追踪，但实时检测标志并不是追踪，若在多目标运动物体中没有做到区分不同的目标，就不算是追踪。

##### 追踪思路

1.对每一帧进行运动目标检测
2.对检测到的物体进行判断，判断是否是存在的运动目标，是则添加运动轨迹，不是则当作新的目标。
3.画出运动轨迹等
如何判断当前已检测到的目标是否是已经存在的运动目标？
这里我刚开始选取的是简单的轮廓面积已经轮廓外接矩形等特征，因为轮廓获得的不稳定，显然效果不是很好。这里采用的是预测模型，在上一篇鼠标运动的预测中。简单来说就是用检测到的运动目标与预测的位置进行比较，若在一定误差内就算追踪成功。

## 运动视频下载

网上有很多的视频库，我下载视频库的链接

## 代码

头文件

#pragma once
#ifndef TrackObject
#define TrackObject
#include<opencv2\opencv.hpp>
#include<iostream>
using namespace std;
using namespace cv;
class Blob {
public:
vector<Point>_contour;
vector<Point>centerPos;
Point preditNextCenter;
Point currentCenter;//first
Rect currentRect;//first
double currentArea;//first
double currentRate;//first
//bool isStillBeTracked;//是否还在被跟踪
//int FramesWithoutMatched;
Blob(vector<Point>con);
void Predit();

};
#endif // !TrackObject



Main

//问题：由于光照的影响很不稳定（对于寻找的轮廓），光照补偿（解决阴影）和去除光照

#include "ObjectTrack.h"
double DistanceBetwwenPoint(Point p1,Point p2)
{
int x =abs(p1.x - p2.x);
int y = abs(p1.y - p2.y);
double res = sqrt(pow(x, 2)) + sqrt(pow(y, 2));
//cout << "距离为：" << res << endl;
return res;
//sqrt(里面参数接受为double型)
}
vector<Blob>Blobs;
int main()
{
VideoCapture  cap("Walk1.mpeg");//这个视频有轻微抖动现象,利用高斯滤波

if (!cap.isOpened()) { cout << "读取视频错误" << endl; return 0; }
Mat cur, pre;
cap >> pre;
GaussianBlur(pre,pre,Size(3,3),0.8);
cvtColor(pre,pre,CV_BGR2GRAY);

while (1)
{
cap >> cur;

if (cur.empty()) { break; }//作用相当于判断是否到达视频最后一帧

GaussianBlur(cur,cur,Size(3,3),0.8);
cvtColor(cur,cur,CV_BGR2GRAY);

/*具体实现步骤*/
//1.背景减除法检测运动物体
Mat df;//difference frame
absdiff(cur, pre,df );
threshold(df,df,70,255,CV_THRESH_BINARY);
vector<vector<Point>>contours;//找到的轮廓均存在contours
findContours(df,contours,CV_RETR_EXTERNAL,CV_CHAIN_APPROX_SIMPLE);

vector<Blob>possibleBlobs;

for (size_t i = 0; i < contours.size(); i++)
{//筛选出可能的目标对象，根据视频中的背景设置筛选条件
Blob tem(contours[i]);
//if (tem.currentArea > 60&&tem.currentRate<1.0) {
if (tem.currentArea > 100) {
possibleBlobs.push_back(tem);

}
}
cout << "每次检测出来的possible个数" << possibleBlobs.size() << endl;

/*
2.跟踪队列为空则直接把possibleBlob全部放入跟踪队列
其他则进行判断，判断是已有的跟踪对象，则把中心点推入已有的跟踪对象，
若是新的跟踪对象直接放入跟踪队列里
*/
if (Blobs.size()==0) {//跟踪队列为空
for (auto &c : possibleBlobs)
{
Blobs.push_back(c);
}
cout << "目标队列为空时第一次加入目标队列追踪的个数"<<Blobs.size() << endl;
}
else {
for (auto &tem : Blobs)
{
tem.Predit();
}
/*
3.选作什么作为不同的跟踪目标之间的区分呢
首先尝试过直接利用已找到的轮廓作为区分，例如轮廓的面积和轮廓外界矩形特征等，但是区分效果很不好
这里利用的是鼠标运动检测中的预测目标点，若预测的目标点小于规定值则认为是同一个目标。

*/
for (auto &tem : possibleBlobs)
{
int flag = false;
for (auto &target : Blobs)
{
if (DistanceBetwwenPoint(tem.currentCenter, target.preditNextCenter) <50.0 )
{
target.centerPos.push_back(tem.currentCenter);
flag = true;
break;
}
}

if (flag == false)Blobs.push_back(tem);//作为新目标被跟踪
}
}
/*for (auto &tem : possibleBlobs)
{//不正确的写法
int flag = false;//匹配标志
for (auto &exist : Blobs)
{
if (abs(tem.currentArea - exist.currentArea) <= scale && abs(tem.currentRate - exist.currentRate) <= scale)
{
exist.centerPos.push_back(tem.currentCenter);
flag = true;
break;
}
}
if (flag == false)Blobs.push_back(tem);
}*/

cout <<"追踪对象有:"<< Blobs.size() << endl;
for (int i = 0; i < Blobs.size(); i++)
{
int size = Blobs[i].centerPos.size();
cout << "第" << i << "个追踪对象内的centerPos点数:" << size << endl;

for (int k= 0;k < size; k++)
{
circle(cur, Blobs[i].centerPos[k],1,Scalar(255,0,0),1,8);
}

if (size == 1)
{
rectangle(cur, Blobs[i].currentRect, Scalar(255, 0, 0), 1, 8);
}
else
{
Point p1;
p1.x = Blobs[i].centerPos[size - 1].x - Blobs[i].currentRect.width*0.5;
p1.y= Blobs[i].centerPos[size - 1].y - Blobs[i].currentRect.height*0.5;
Rect rect(p1.x,p1.y,Blobs[i].currentRect.width,Blobs[i].currentRect.height);
rectangle(cur, rect, Scalar(255, 0, 0), 1, 8);
}

}
//drawContours(cur,contours,-1,Scalar(255,0,0),1,8);
imshow("df",df);
imshow("cur",cur);
//pre=cur;//加上这一句就是帧差法
if (waitKey(100) == 27)break;//或者当前的帧数是最后一帧,采用判断当前读取的帧数是否非空即可
}
cap.release();
destroyAllWindows();
system("pause");
return 0;
}



其中cpp文件里有初始化和预测函数，预测方法与鼠标移动预测类似，所以就不贴了。

展开全文

...