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

帧间差分法

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

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

参考方法:
#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这两种方法。

//参考链接:
// 方法: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();
}

光流场法

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

原理:

android OpenCV 移动物体识别 opencv 移动物体检测_ide

/*稀疏光流阀*/
#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;
}