今天朋友问我要一个车上充满点点的图片,然后我第一时间想到了光流法,然后想到了之前总结的运动物体检测的几个方法,还在有道云笔记里面,所以打算搬迁过来。
帧间差分法
定义:利用相邻的两帧或者三帧图像,利用像素之间的差异性,判断是否有运动目标
基本步骤:相邻帧相减---阈值处理---去除噪声---膨胀联通---查找轮廓---绘制外接矩形
参考方法:
#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中,只有BackgroundSubtractorKNN和BackgroundSubtractorMOG2这两种方法。
//参考链接:
// 方法: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 fgMaskMOG2; //通过MOG2方法得到的掩码图像fgmask
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 ){
if(!capture.read(frame)) {
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);
pMOG2->apply(frame, fgMaskMOG2); //更新背景模型
frame.copyTo(segm); //建立一个当前frame的副本
findContours(fgMaskMOG2, contours, hierarchy,
RETR_TREE, CHAIN_APPROX_SIMPLE,Point(0,0)); //检测轮廓
vector <vector<Point> > contours_poly( contours.size());
vector <Point2f> center( contours.size());
vector <float> radius( contours.size());
for( int i = 0; i < contours.size(); i++){
//findContours后的轮廓信息contours可能过于复杂不平滑,
//可以用approxPolyDP函数对该多边形曲线做适当近似
approxPolyDP( Mat(contours[i]), contours_poly[i], 3, true);
//得到轮廓的外包络圆
minEnclosingCircle( contours_poly[i], center[i], radius[i]);
}
//对所得到的轮廓进行一定的筛选
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);
string frameNumberString = ss.str();
putText(frame, frameNumberString.c_str(), cv::Point(15, 15),
FONT_HERSHEY_SIMPLEX, 0.5 , cv::Scalar(0,0,0));
//显示
imshow("frame", frame);
imshow("Segm", segm);
imshow("FG Mask MOG 2", fgMaskMOG2);
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)
{
VideoCapture capture("/home/demon/CLionProjects/Radar/cmake-build-debug/110_90.avi");
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
Mat mask = Mat::zeros(old_frame.size(), old_frame.type());
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
// line(mask,p1[i], p0[i], Scalar(0,0,255), 2);
// circle(frame, p1[i], 5, Scalar(0,0,255), -1);
circle(mask, p1[i], 10, Scalar(0,0,255), -1);
}
}
Mat img;
add(frame, mask, 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);
frame=imread(filename);
if(frame.empty()) break;
writer<<frame;
}
cout<<"write end!"<<endl;
destroyAllWindows();
return 0;
}