光流法

原文:http://blog.csdn.net/crzy_sparrow/article/details/7407604

本文目录:

一.基于特征点的目标跟踪的一般方法

二.光流法

三.opencv中的光流法函数

四.用类封装基于光流法的目标跟踪方法

五.完整代码

六.参考文献

一.基于特征点的目标跟踪的一般方法

基于特征点的跟踪算法大致可以分为两个步骤:

1)探测当前帧的特征点;

2)通过当前帧和下一帧灰度比较,估计当前帧特征点在下一帧的位置;

3)过滤位置不变的特征点,余下的点就是目标了。

很显然,基于特征点的目标跟踪算法和1),2)两个步骤有关。特征点可以是Harris角点(见我的另外一篇博文),也可以是边缘点等等,而估计下一帧位置的方法也有不少,比如这里要讲的光流法,也可以是卡尔曼滤波法(咱是控制系的,上课经常遇到这个,所以看光流法看着看着就想到这个了)。

本文中,用改进的Harris角点提取特征点(见我另一篇博文:http://blog.csdn.net/crzy_sparrow/article/details/7391511),用Lucas-Kanade光流法实现目标跟踪。

二.光流法

这一部分《learing opencv》一书的第10章Lucas-Kanade光流部分写得非常详细,推荐大家看书。我这里也粘帖一些选自书中的内容。

另外我对这一部分附上一些个人的看法(谬误之处还望不吝指正):

1.首先是假设条件:

(1)亮度恒定,就是同一点随着时间的变化,其亮度不会发生改变。这是基本光流法的假定(所有光流法变种都必须满足),用于得到光流法基本方程;

(2)小运动,这个也必须满足,就是时间的变化不会引起位置的剧烈变化,这样灰度才能对位置求偏导(换句话说,小运动情况下我们才能用前后帧之间单位位置变化引起的灰度变化去近似灰度对位置的偏导数),这也是光流法不可或缺的假定;

(3)空间一致,一个场景上邻近的点投影到图像上也是邻近点,且邻近点速度一致。这是Lucas-Kanade光流法特有的假定,因为光流法基本方程约束只有一个,而要求x,y方向的速度,有两个未知变量。我们假定特征点邻域内做相似运动,就可以连立n多个方程求取x,y方向的速度(n为特征点邻域总点数,包括该特征点)。

2.方程求解

多个方程求两个未知变量,又是线性方程,很容易就想到用最小二乘法,事实上opencv也是这么做的。其中,最小误差平方和为最优化指标。

3.好吧,前面说到了小运动这个假定,聪明的你肯定很不爽了,目标速度很快那这货不是二掉了。幸运的是多尺度能解决这个问题。首先,对每一帧建立一个高斯金字塔,最大尺度图片在最顶层,原始图片在底层。然后,从顶层开始估计下一帧所在位置,作为下一层的初始位置,沿着金字塔向下搜索,重复估计动作,直到到达金字塔的底层。聪明的你肯定发现了:这样搜索不仅可以解决大运动目标跟踪,也可以一定程度上解决孔径问题(相同大小的窗口能覆盖大尺度图片上尽量多的角点,而这些角点无法在原始图片上被覆盖)。

#include "opencv2/opencv.hpp"
#include <sstream>
#include <iomanip>
using namespace std;
using namespace cv;

class FrameProcessor;
//帧处理基类
class FrameProcessor{
    public:
        virtual void process(Mat &input,Mat &ouput)=0;
};

//特征跟踪类,继承自帧处理基类
class FeatureTracker :  public FrameProcessor{
    Mat gray;  //当前灰度图
    Mat gray_prev;  //之前的灰度图
    vector<Point2f> points[2];//前后两帧的特征点
    vector<Point2f> initial;//初始特征点
    vector<Point2f> features;//检测到的特征
    int max_count; //要跟踪特征的最大数目
    double qlevel; //特征检测的指标
    double minDist;//特征点之间最小容忍距离
    vector<uchar> status; //特征跟踪状态
    vector<float> err; //跟踪时的错误
public:
    FeatureTracker():max_count(500),qlevel(0.01),minDist(10.){}
    void process(Mat &frame,Mat &output){
        //得到灰度图
        cvtColor (frame,gray,CV_BGR2GRAY);
        frame.copyTo (output);
        //特征点太少了,重新检测特征点
        if(addNewPoint()){
            detectFeaturePoint ();
            //插入检测到的特征点
            points[0].insert (points[0].end (),features.begin (),features.end ());
            initial.insert (initial.end (),features.begin (),features.end ());
        }
        //第一帧
        if(gray_prev.empty ()){
                gray.copyTo (gray_prev);
        }
        //根据前后两帧灰度图估计前一帧特征点在当前帧的位置
        //默认窗口是15*15
        calcOpticalFlowPyrLK (
                gray_prev,//前一帧灰度图
                gray,//当前帧灰度图
                points[0],//前一帧特征点位置
                points[1],//当前帧特征点位置
                status,//特征点被成功跟踪的标志
                err);//前一帧特征点点小区域和当前特征点小区域间的差,根据差的大小可删除那些运动变化剧烈的点
        int k = 0;
        //去除那些未移动的特征点
        for(int i=0;i<points[1].size ();i++){
            if(acceptTrackedPoint (i)){
                initial[k]=initial[i];
                points[1][k++] = points[1][i];
            }
        }
        points[1].resize (k);
        initial.resize (k);
        //标记被跟踪的特征点
        handleTrackedPoint (frame,output);
        //为下一帧跟踪初始化特征点集和灰度图像
        std::swap(points[1],points[0]);
        cv::swap(gray_prev,gray);
    }

    void detectFeaturePoint(){
        goodFeaturesToTrack (gray,//图片
                                 features,//输出特征点
                                 max_count,//特征点最大数目
                                 qlevel,//质量指标
                                 minDist);//最小容忍距离
    }
    bool addNewPoint(){
            //若特征点数目少于10,则决定添加特征点
        return points[0].size ()<=10;
    }

    //若特征点在前后两帧移动了,则认为该点是目标点,且可被跟踪
    bool acceptTrackedPoint(int i){
        return status[i]&&
                (abs(points[0][i].x-points[1][i].x)+
                  abs(points[0][i].y-points[1][i].y) >2);
    }

    //画特征点
    void  handleTrackedPoint(Mat &frame,Mat &output){
            for(int i=0;i<points[i].size ();i++){
                //当前特征点到初始位置用直线表示
                line(output,initial[i],points[1][i],Scalar::all (0));
                //当前位置用圈标出
                circle(output,points[1][i],3,Scalar::all(0),(-1));
            }
        }
};

class VideoProcessor{
private:
    VideoCapture caputure;
    //写视频流对象
    VideoWriter writer;
    //输出文件名
    string Outputfile;

    int currentIndex;
    int digits;
    string extension;
    FrameProcessor *frameprocessor;
    //图像处理函数指针
    void (*process)(Mat &,Mat &);
    bool callIt;
    string WindowNameInput;
    string WindowNameOutput;
    //延时
    int delay;
    long fnumber;
    //第frameToStop停止
    long frameToStop;
    //暂停标志
    bool stop;
    //图像序列作为输入视频流
    vector<string> images;
    //迭代器
public:
    VideoProcessor() : callIt(true),delay(0),fnumber(0),stop(false),digits(0),frameToStop(-1){}
   //设置图像处理函数
    void setFrameProcessor(void (*process)(Mat &,Mat &)){
        frameprocessor = 0;
        this->process = process;
        CallProcess ();
    }
    //打开视频
    bool setInput(string filename){
        fnumber = 0;
        //若已打开,释放重新打开
        caputure.release ();
        return caputure.open (filename);
    }
    //设置输入视频播放窗口
    void displayInput(string wn){
        WindowNameInput = wn;
        namedWindow (WindowNameInput);
    }
    //设置输出视频播放窗口
    void displayOutput(string wn){
        WindowNameOutput = wn;
        namedWindow (WindowNameOutput);
    }
    //销毁窗口
    void dontDisplay(){
        destroyWindow (WindowNameInput);
        destroyWindow (WindowNameOutput);
        WindowNameInput.clear ();
        WindowNameOutput.clear ();
    }

    //启动
    void run(){
        Mat frame;
        Mat output;
        if(!isOpened())
            return;
        stop = false;
        while(!isStopped()){
            //读取下一帧
            if(!readNextFrame(frame))
                break;
            if(WindowNameInput.length ()!=0)
                imshow (WindowNameInput,frame);
            //处理该帧
            if(callIt){
                if(process)
                    process(frame,output);
                else if(frameprocessor)
                    frameprocessor->process (frame,output);
            }
            else{
                output = frame;
            }
            if(Outputfile.length ()){
                    cvtColor (output,output,CV_GRAY2BGR);
                    writeNextFrame (output);
              }
            if(WindowNameOutput.length ()!=0)
                imshow (WindowNameOutput,output);
            //按键暂停,继续按键继续
            if(delay>=0&&waitKey (delay)>=0)
                waitKey(0);
            //到达指定暂停键,退出
            if(frameToStop>=0&&getFrameNumber()==frameToStop)
                stopIt();
        }
    }
    //暂停键置位
    void stopIt(){
        stop = true;
    }
    //查询暂停标志位
    bool isStopped(){
        return stop;
    }
    //返回视频打开标志
    bool isOpened(){
       return  caputure.isOpened ()||!images.empty ();
    }
    //设置延时
    void setDelay(int d){
        delay = d;
    }
    //读取下一帧
    bool readNextFrame(Mat &frame){
        if(images.size ()==0)
            return caputure.read (frame);
        else{
            if(itImg!=images.end()){
                frame = imread (*itImg);
                itImg++;
                return frame.data?1:0;
            }
            else
                return false;
        }
    }

    void CallProcess(){
        callIt = true;
    }
    void  dontCallProcess(){
        callIt = false;
    }
    //设置停止帧
    void stopAtFrameNo(long frame){
        frameToStop = frame;
    }
   // 获得当前帧的位置
    long getFrameNumber(){
        long fnumber = static_cast<long>(caputure.get ((CV_CAP_PROP_POS_FRAMES)));
        return fnumber;
    }

     //获得帧大小
       Size getFrameSize() {
        if (images.size()==0) {
            // 从视频流获得帧大小
            int w= static_cast<int>(caputure.get(CV_CAP_PROP_FRAME_WIDTH));
            int h= static_cast<int>(caputure.get(CV_CAP_PROP_FRAME_HEIGHT));
            return Size(w,h);
            }
        else {
                //从图像获得帧大小
                cv::Mat tmp= cv::imread(images[0]);
                return (tmp.data)?(tmp.size()):(Size(0,0));
            }
          }

   //获取帧率
    double getFrameRate(){
        return caputure.get(CV_CAP_PROP_FPS);
    }
    vector<string>::const_iterator itImg;
    bool setInput (const vector<string> &imgs){
        fnumber = 0;
        caputure.release ();
        images = imgs;
        itImg = images.begin ();
        return true;
    }

    void  setFrameProcessor(FrameProcessor *frameprocessor){
        process = 0;
        this->frameprocessor = frameprocessor;
        CallProcess ();
    }

    //获得编码类型
    int getCodec(char codec[4]) {
        if (images.size()!=0)
            return -1;
        union { // 数据结构4-char
            int value;
            char code[4];
        } returned;
        //获得编码值
        returned.value= static_cast<int>(
        caputure.get(CV_CAP_PROP_FOURCC));
        // get the 4 characters
        codec[0]= returned.code[0];
        codec[1]= returned.code[1];
        codec[2]= returned.code[2];
        codec[3]= returned.code[3];
        return returned.value;
    }

    bool setOutput(const string &filename,int codec = 0,double framerate = 0.0,bool isColor = true){
        //设置文件名
        Outputfile = filename;
        //清空扩展名
        extension.clear ();
        //设置帧率
        if(framerate ==0.0){
            framerate = getFrameRate ();
        }
        //获取输入原视频的编码方式
        char c[4];
        if(codec==0){
            codec = getCodec(c);
        }
        return writer.open(Outputfile,
                           codec,
                           framerate,
                           getFrameSize(),
                           isColor);
    }

    //输出视频帧到图片fileme+currentIndex.ext,如filename001.jpg
    bool setOutput (const string &filename,//路径
                    const string &ext,//扩展名
                    int numberOfDigits=3,//数字位数
                    int startIndex=0 ){//起始索引
           if(numberOfDigits<0)
               return false;
           Outputfile = filename;
           extension = ext;
           digits = numberOfDigits;
           currentIndex = startIndex;
           return true;
    }

    //写下一帧
    void writeNextFrame(Mat &frame){
        //如果扩展名不为空,写到图片文件中
        if(extension.length ()){
            stringstream ss;
            ss<<Outputfile<<setfill(‘0‘)<<setw(digits)<<currentIndex++<<extension;
            imwrite (ss.str (),frame);
        }
        //反之,写到视频文件中
        else{
            writer.write (frame);
        }
    }

};

    //帧处理函数:canny边缘检测
    void canny(cv::Mat& img, cv::Mat& out) {
        //灰度变换
        if (img.channels()==3)
            cvtColor(img,out,CV_BGR2GRAY);
        // canny算子求边缘
        Canny(out,out,100,200);
        //颜色反转,看起来更舒服些
        threshold(out,out,128,255,cv::THRESH_BINARY_INV);
    }

int main(int argc, char *argv[])
{
    VideoProcessor processor;
    FeatureTracker tracker;
    //打开输入视频
    processor.setInput ("bike.avi");
    processor.displayInput ("Current Frame");
    processor.displayOutput ("Output Frame");
    //设置每一帧的延时
    processor.setDelay (1000./processor.getFrameRate ());
    //设置帧处理函数,可以任意
    processor.setFrameProcessor (&tracker);
    //   processor.setOutput ("./bikeout.avi");
    //    processor.setOutput ("bikeout",".jpg");
    processor.run ();
    return 0;
}

运行结果:

六.参考文献

【1】The classic article by B. Lucas and T. Kanade, An iterative image registration technique with an application to stereo vision in Int. Joint Conference in Artificial Intelligence, pp. 674-679,1981, that describes the original feature point tracking algorithm.
【2】The article by J. Shi and C. Tomasi, Good Features to Track in IEEE Conference on Computer Vision and Pattern Recognition, pp. 593-600, 1994, that describes an improved version of the original feature point tracking algorithm.

时间: 2024-07-29 22:13:51

光流法的相关文章

OpenCv_Image与光流法中设置ROI区域

无论是在图片显示中或者是在光流法寻找角点的过程中,我们都会遇到ROI这个东西.它的作用就是让我们能够专注于图像或者当前视频帧中的某一块区域(我们称之为感兴趣区域) 进行处理,而不是对整个图像或者是整个视频帧进行处理,这不仅能够排除掉一些不必要的误差干扰,还能减少运算量.接下来分别说明来图像和光流法视频中如何设置ROI. 图像设置ROI 代码如下: /* * Description : setting ROI in image * Author : Liulongpo * Date : 2015年

【图像处理】openCV光流法追踪运动物体

openCV光流法追踪运动物体 email:[email protected] 一.光流简单介绍 摘自:zouxy09 光流的概念是Gibson在1950年首先提出来的.它是空间运动物体在观察成像平面上的像素运动的瞬时速度,是利用图像序列中像素在时间域上的变化以及相邻帧之间的相关性来找到上一帧跟当前帧之间存在的相应关系.从而计算出相邻帧之间物体的运动信息的一种方法.一般而言,光流是因为场景中前景目标本身的移动.相机的运动,或者两者的共同运动所产生的. 研究光流场的目的就是为了从图片序列中近似得到

光流法optical flow

光流是图像亮度的运动信息描述.光流法计算最初是由Horn和Schunck于1981年提出的,创造性地将二维速度场与灰度相联系,引入光流约束方程,得到光流计算的基本算法.光流计算基于物体移动的光学特性提出了2个假设: ①运动物体的灰度在很短的间隔时间内保持不变: ②给定邻域内的速度向量场变化是缓慢的. 算法原理 假设图像上一个像素点(x,y),在t时刻的亮度为E(x+Δx,y+Δy,t+Δt),同时用u(x,y0和v(x,y)来表示该点光流在水平和垂直方向上的移动分量: u=dx/dt v=dy/

运动物体检测——光流法(摄像机固定)

前面的一篇文章谈了高斯背景模型在运动物体检测中的应用.本文主要讨论另一种方法——光流法.与高斯背景模型的方法不同,光流法可以用于摄像机固定和摄像机运动的情形,但本文只就摄像机固定的情况进行讨论,即不涉及摄像机运动预测问题. 光流法的介绍 在空间中,运动可以用运动场描述.而在一个图像平面上,物体的运动往往是通过图像序列中不同图象灰度分布的不同体现的.从而,空间中的运动场转移到图像上就表示为光流场,光流场反映了图像上每一点灰度的变化趋势. 光流可以看作带有灰度的像素点在图像平面运动产生的瞬时速度场.

LK 光流法简介

前言 若假定一个局部区域的像素运动是一致的,则可以用这个新的约束条件替代前文中提到的全局速度平滑约束条件.这种光流算法就叫做 LK 光流法. LK 光流法的推导 首先,需要推导出光流约束方程. 这一步和前文 HS 光流法中的光流约束方程推导是一样的. 分析某像素点附近 n x n 区域,假定局部区域的像素运动是一致的,则可以建立类似如下形式的 n * n 个方程: (1) 如果在这个窗口内包含两条或以上边缘,则可以求解此系统方程. 下面将此系统方程写成向量式: (2) 这里的 u 是速度向量.

Horn&ndash;Schunck 光流法与其算法理解(gup cuda)

1. 基于Horn-Schunck模型的光流算法 1.1     光流的约束条件 光流 的假设条件认为图像序列,在时间t 的某一像素点与在时间t+1的这一像素点的偏移量保持不变,即 .这就是灰度值守恒假设,通过Taylor展开,就能得到光流的约束条件(OFC): ,其中下标表示图像的梯度. 1.2     Horn-Schunck 模型 1981年,Horn和Schunck根据同一个运动物体的光流场具有连续.平滑的特点, 提出一个附加约束条件,将光流场的整体平滑约束转换为一个变分的问题.它的能量

L-K光流法---视频追踪

光流法基本概念介绍如下:https://chunqiu.blog.ustc.edu.cn/?p=661 原文作者阐述的十分详细,按照我一个行外人的理解来说就是一个运动模糊轨迹实时检测.视频光流检测所有代码如下: % 光流法测试--视频 clc clear close all % 读取文件对象 videoReader = vision.VideoFileReader('D:\桌面\图像处理\00-1- 1.avi','ImageColorSpace','Intensity','VideoOutpu

HS 光流法详解

前言 本文较为详细地介绍了一种经典的光流法 - HS 光流法. 光流法简介 当人的眼睛与被观察物体发生相对运动时,物体的影像在视网膜平面上形成一系列连续变化的图像,这一系列变化的图像信息不断 "流过" 视网膜,好像是一种光的  "流",所以被称为光流. 光流是基于像素点定义的,所有光流的集合称为光流场.通过对光流场进行分析,可以得到物体相对观察者的运动场.在这过程中分析的算法称为光流法. HS 光流法的推导 HS光流计算基于物体移动的光学特性的两个假设: 1. 运动

目标跟踪之光流法---光流法简单介绍

光流的概念是Gibson在1950年首先提出来的.它是空间运动物体在观察成像平面上的像素运动的瞬时速度,是利用图像序列中像素在时间域上的变化以及相邻帧之间的相关性来找到上一帧跟当前帧之间存在的对应关系,从而计算出相邻帧之间物体的运动信息的一种方法.一般而言,光流是由于场景中前景目标本身的移动.相机的运动,或者两者的共同运动所产生的.其计算方法可以分为三类: (1)基于区域或者基于特征的匹配方法: (2)基于频域的方法: (3)基于梯度的方法: 简单来说,光流是空间运动物体在观测成像平面上的像素运