Horn-Schunck光流法

关于光流法请看我之前的博客Lukas-Kanade光流法。这里介绍Horn-Schunck光流法。

Horn-Schunck光流法求得的是稠密光流,需要对每一个像素计算光流值,计算量比较大。而Lucas-Kanade光流法只需计算若干点的光流,是一种稀疏光流。

数学原理这里就不介绍了,直接说算法步骤。

用uij与vij分别表示图像像素点(i,j)处的水平方向光流值与垂直方向光流值,每次迭代后的更新方程为
在这里插入图片描述

n为迭代次数,lamda反映了对图像数据及平滑约束的可信度,当图像数据本身含有较大噪声时,此时需要加大lamda的值,相反,当输入图像含有较少的噪声时,此时可减小lamda的值。

代表u邻域与v邻域的平均值,一般采用相应4邻域内的均值

在这里插入图片描述
也可以采用33、55的窗口用模板平滑,窗口不宜过大,过大会破坏光流假设。

Ix、Iy分别是图像对x、y的偏导数。It是两帧图像间对时间的导数。
在这里插入图片描述

当然你也可以考虑相邻像素及相邻两帧图像的影响,Horn-Schunck 提出通过 4 次有限差分来得到
在这里插入图片描述

这里只考虑了前后两帧图像。考虑连续三帧图像的话有如下方法:

一种性能更优的 3D-Sobel 算子 如下图所示。该算子在x 、y 、t方向上分别使用不同的模板对连续3帧图像进行卷积计算 得出中间帧的位于模板中心的像素在三个方向上的梯度 。
在这里插入图片描述

迭代一定次数后u、v收敛,光流计算停止。在实际的计算中迭代初值可取U(0) =0、V(0)=0。

算法改进

对于一般场景基本等式只有在图像中灰度梯度值较大的点处才成立。因此为了增强算法的稳定性和准确性 我们仅在梯度较大的点处才使用亮度恒常性约束,而在梯度较小的点处只使用流场一致性约束。定义如下权函数

在这里插入图片描述

下面是我的实现,使用了图像金字塔,关于图像金字塔,请看Lukas-Kanade光流法。(写代码时传错一个参数,调了几个小时哭)

[cpp] view plaincopyprint?
#ifndef __HORNSCHUNCK__  
#define __HORNSCHUNCK__  
class HornSchunckTracker  
{  
private:  
    unsigned int max_pyramid_layer;  
    unsigned int original_imgH;  
    unsigned int original_imgW;  
    BYTE**pre_pyr;//the pyramid of previous frame image,img1_pyr[0] is of max size  
    BYTE**next_pyr;//the frame after img1_pyr  
    int*height;  
    int*width;  
    double*optical_field_U;  
    double*optical_field_V;  
    bool isusepyramid;  
    double lamda;//取20  
    const double precision = 1;  
    const int maxiteration=300;  
    double threshold;//最小的光流阈值  
    double scale_factor;//缩放因子  
private:  
    void get_max_pyramid_layer();  
    void pyramid_down(BYTE*&src_gray_data, const int src_h,  
        const int src_w, BYTE*& dst, int&dst_h, int&dst_w);  
    void pyramid_up(double*src,int srcW,int srcH,double*dst,int dstW,int dstH);  
    void lowpass_filter(double*&src, const int H, const int W, double*&smoothed);  
    void get_fx_fy_ft(BYTE*img1, BYTE*img2, int w, int h, double*fx, double*fy, double*ft);  
    void build_pyramid(BYTE**&original_gray);  
    double get_average4(double*src, const int height, const int width, const int i, const int j);  
    void  bilinear(double* lpSrc, double* lpDst, int nW, int nH, int H1, int W1);  
    void  bilinear(BYTE* lpSrc, BYTE* lpDst, int nW, int nH, int H1, int W1);  
      
  
  
public:  
    HornSchunckTracker();  
    ~HornSchunckTracker();  
    void get_pre_frame(BYTE*&gray);//use only at the beginning  
    void discard_pre_frame();  
    //set the next frame as pre_frame,must dicard pre_pyr in advance  
    void get_pre_frame();  
    //use every time,must after using get_pre_frame(BYTE**pyr)  
    void get_next_frame(BYTE*&gray);  
    void get_info(const int nh, const int nw);  
    void set_paras(double lamda,double threshold,double scalefactor);  
    void run_single_frame();  
    void HornSchunck();  
    void get_optical_flow(double*&u, double*&v);  
};  
  
#endif  



[cpp] view plaincopyprint?
#include "stdafx.h"  
#include "HornSchunckTracker.h"  
  
  
HornSchunckTracker::HornSchunckTracker()  
{  
    isusepyramid = true;  
}  
  
  
HornSchunckTracker::~HornSchunckTracker()  
{  
    for (int i = 0; i < max_pyramid_layer; i++)  
    {  
        if (pre_pyr[i])  
            delete[]pre_pyr[i];  
        if (next_pyr[i])  
            delete[]next_pyr[i];  
    }  
    delete[]pre_pyr;  
    delete[]next_pyr;  
    if (height)  
        delete[]height;  
    if (width)  
        delete[]width;  
  
}  
void HornSchunckTracker::get_max_pyramid_layer()  
{  
    double minsize = 80;  
    double temp = original_imgH > original_imgW ?  
    original_imgW : original_imgH;  
    double tt = log(temp / minsize) / log(scale_factor);  
    if (tt>4)  
    {  
        max_pyramid_layer = 5;  
        return;  
    }  
      
    max_pyramid_layer = tt;  
}  
  
void HornSchunckTracker::build_pyramid(BYTE**&pyramid)  
{  
    for (int i = 1; i < max_pyramid_layer; i++)  
    {  
        pyramid_down(pyramid[i - 1], height[i - 1],  
            width[i - 1], pyramid[i], height[i], width[i]);  
    }  
}  
  
  
void HornSchunckTracker::pyramid_down(BYTE*&src_gray_data,  
    const int src_h, const int src_w, BYTE*& dst, int&dst_h, int&dst_w)  
{  
    dst_h = src_h / scale_factor;  
    dst_w = src_w / scale_factor;  
      
    assert(dst_w > 3 && dst_h > 3);  
    //BYTE*smoothed = new BYTE[src_h*src_w];  
    dst = new BYTE[dst_h*dst_w];  
    //lowpass_filter(src_gray_data, src_h, src_w,smoothed);  
    bilinear(src_gray_data, dst, src_w, src_h, dst_h, dst_w);  
  
    /*for (int i = 0; i < dst_h - 1; i++) 
        for (int j = 0; j < dst_w - 1; j++) 
        { 
            int srcY = 2 * i + 1; 
            int srcX = 2 * j + 1; 
            double re = src_gray_data[srcY*src_w + srcX] * 0.25; 
            re += src_gray_data[(srcY - 1)*src_w + srcX] * 0.125; 
            re += src_gray_data[(srcY + 1)*src_w + srcX] * 0.125; 
            re += src_gray_data[srcY*src_w + srcX - 1] * 0.125; 
            re += src_gray_data[srcY*src_w + srcX + 1] * 0.125; 
            re += src_gray_data[(srcY - 1)*src_w + srcX + 1] * 0.0625; 
            re += src_gray_data[(srcY - 1)*src_w + srcX - 1] * 0.0625; 
            re += src_gray_data[(srcY + 1)*src_w + srcX - 1] * 0.0625; 
            re += src_gray_data[(srcY + 1)*src_w + srcX + 1] * 0.0625; 
            dst[i*dst_w + j] = re; 
        } 
    for (int i = 0; i < dst_h; i++) 
        dst[i*dst_w + dst_w - 1] = dst[i*dst_w + dst_w - 2]; 
    for (int i = 0; i < dst_w; i++) 
        dst[(dst_h - 1)*dst_w + i] = dst[(dst_h - 2)*dst_w + i];*/  
}  
  
  
void HornSchunckTracker::get_pre_frame(BYTE*&gray)//use only at the beginning  
{  
    pre_pyr[0] = gray;  
    build_pyramid(pre_pyr);  
    //save_gray("1.bmp", pre_pyr[1], height[1], width[1]);  
}  
  
void  HornSchunckTracker::discard_pre_frame()  
{  
    for (int i = 0; i < max_pyramid_layer; i++)  
        delete[]pre_pyr[i];  
}  
//set the next frame as pre_frame,must dicard pre_pyr in advance  
void  HornSchunckTracker::get_pre_frame()  
{  
    for (int i = 0; i < max_pyramid_layer; i++)  
        pre_pyr[i] = next_pyr[i];  
}  
//use every time,must after using get_pre_frame(BYTE**pyr)  
void  HornSchunckTracker::get_next_frame(BYTE*&gray)  
{  
    next_pyr[0] = gray;  
    build_pyramid(next_pyr);  
    //save_gray("1.bmp", next_pyr[1], height[1], width[1]);  
}  
  
void HornSchunckTracker::get_info(const int nh, const int nw)  
{  
    original_imgH = nh;  
    original_imgW = nw;  
    if (isusepyramid)  
        get_max_pyramid_layer();  
    else  
        max_pyramid_layer = 1;  
    pre_pyr = new BYTE*[max_pyramid_layer];  
    next_pyr = new BYTE*[max_pyramid_layer];  
    height = new int[max_pyramid_layer];  
    width = new int[max_pyramid_layer];  
    height[0] = nh;  
    width[0] = nw;  
}  
  
  
//低通滤波  
void HornSchunckTracker::lowpass_filter(double*&src, const int H, const int W, double*&smoothed)  
{  
    //tackle with border  
    for (int i = 0; i < H; i++)  
    {  
        smoothed[i*W] = src[i*W];  
        smoothed[i*W + W - 1] = src[i*W + W - 1];  
    }  
    for (int i = 0; i < W; i++)  
    {  
        smoothed[i] = src[i];  
        smoothed[(H - 1)*W + i] = src[(H - 1)*W + i];  
    }  
  
    for (int i = 1; i < H - 1; i++)  
        for (int j = 1; j < W - 1; j++)  
        {  
            double re = 0;  
            re += src[i*W + j] * 0.25;  
            re += src[(i - 1)*W + j] * 0.125;  
            re += src[i*W + j + 1] * 0.125;  
            re += src[i*W + j - 1] * 0.125;  
            re += src[(i + 1)*W + j] * 0.125;  
            re += src[(i - 1)*W + j - 1] * 0.0625;  
            re += src[(i + 1)*W + j - 1] * 0.0625;  
            re += src[(i - 1)*W + j + 1] * 0.0625;  
            re += src[(i + 1)*W + j + 1] * 0.0625;  
            smoothed[i*W + j] = re;  
        }  
  
}  
  
void HornSchunckTracker::get_fx_fy_ft(BYTE*img1, BYTE*img2, int w, int h, double*fx, double*fy, double*ft)  
{  
    for (int i = 0; i < h - 1; i++)  
        for (int j = 0; j < w - 1; j++)  
        {  
            fx[i*w + j] = 0.25*(img1[i*w + j + 1] - img1[i*w + j] + img1[(i + 1)*w + j + 1] - img1[(i + 1)*w + j]  
                + img2[i*w + j + 1] - img2[i*w + j] + img2[(i + 1)*w + j + 1] - img2[(i + 1)*w + j]);  
            fy[i*w + j] = 0.25 * (img1[(i + 1)*w + j] - img1[i*w + j] +img1[(i + 1)*w + j + 1] - img1[i*w + j + 1]  
                + img2[(i + 1)*w + j] - img2[i*w + j] + img2[(i + 1)*w + j + 1] - img2[i*w + j + 1]);  
            ft[i*w + j] = 0.25 * (img2[i*w + j] - img1[i*w + j] +img2[(i + 1)*w + j] - img1[(i + 1)*w + j] +  
                img2[(i + 1)*w + j + 1] - img1[(i + 1)*w + j + 1] + img2[i*w + j + 1] - img1[i*w + j + 1]);  
        }  
    for (int i = 0; i < h; i++)  
    {  
        //fx[i*w] = fx[i*w + w - 2];  
        fx[i*w + w - 1] = fx[i*w + w - 2];  
        //fy[i*w] = fy[i*w + w - 2];  
        fy[i*w + w - 1] = fy[i*w + w - 2];  
        //ft[i*w] = ft[i*w + w - 2];  
        ft[i*w + w - 1] = ft[i*w + w - 2];  
    }  
    for (int j = 0; j < w; j++)  
    {  
        //fx[j] = fx[h + j];  
        fx[(h - 1)*w + j] = fx[(h - 2)*w + j];  
        //fy[j] = fy[h + j];  
        fy[(h - 1)*w + j] = fy[(h - 2)*w + j];  
        //ft[j] = ft[h + j];  
        ft[(h - 1)*w + j] = ft[(h - 2)*w + j];  
    }  
  
}  
//取得计算得到的光流值,u、v为out型参数  
void HornSchunckTracker::get_optical_flow(double*&u, double*&v)  
{  
    assert(optical_field_U&&optical_field_V);  
    u = optical_field_U;  
    v = optical_field_V;  
}  
  
//int save_gray(char * savebmp_file, LPBYTE gray, int height, int width);  
//返回求得的光流场,大小为原始图像大小  
void HornSchunckTracker::HornSchunck()  
{  
    //save_gray("22.bmp", pre_pyr[0], height[0], width[0]);  
    //初始化光流场为0  
    if (optical_field_U)  
        delete[]optical_field_U;  
    if (optical_field_V)  
        delete[]optical_field_V;  
    optical_field_U = new double[width[max_pyramid_layer - 1]  
        * height[max_pyramid_layer - 1]];  
    optical_field_V = new double[width[max_pyramid_layer - 1]  
        * height[max_pyramid_layer - 1]];  
    memset(optical_field_U, 0, sizeof(double)*width[max_pyramid_layer - 1]  
        * height[max_pyramid_layer - 1]);  
    memset(optical_field_V, 0, sizeof(double)*width[max_pyramid_layer - 1]  
        * height[max_pyramid_layer - 1]);  
  
    //使用金字塔计算光流  
    for (int i = max_pyramid_layer - 1; i >= 0; i--)  
    {  
        double*Ix = new double[width[i] * height[i]];  
        double*Iy = new double[width[i] * height[i]];  
        double*It = new double[width[i] * height[i]];  
        //求偏导  
        get_fx_fy_ft(pre_pyr[i], next_pyr[i], width[i], height[i], Ix, Iy, It);  
  
        //将光流场平滑  
        double*smoothed_U = new double[width[i] * height[i]];  
        double*smoothed_V = new double[width[i] * height[i]];  
  
        if (i == max_pyramid_layer - 1)  
        {  
            memset(smoothed_U, 0, sizeof(double)*width[i] * height[i]);  
            memset(smoothed_V, 0, sizeof(double)*width[i] * height[i]);  
        }  
        else  
        {  
            lowpass_filter(optical_field_U, height[i], width[i], smoothed_U);  
            lowpass_filter(optical_field_V, height[i], width[i], smoothed_V);  
        }  
        double error = 1000000;  
        int iteration = 0;  
        //迭代计算每个像素的光流,直到收敛或达到最大迭代次数  
        while (error > precision&&iteration < maxiteration)  
        {  
            iteration++;  
            error = 0;  
            //计算该层金字塔的光流  
            for (int j = 0; j < height[i]; j++)  
                for (int k = 0; k < width[i]; k++)  
                {  
  
                    //采用改进方法,光流速度需大于阈值,这样不仅准确度增加,计算量也会减小  
                    double w = Ix[j*width[i] + k] * Ix[j*width[i] + k]  
                        + Iy[j*width[i] + k] * Iy[j*width[i] + k] > threshold ? 1 : 0;  
  
                    double u_pre = optical_field_U[j*width[i] + k];  
                    double v_pre = optical_field_V[j*width[i] + k];  
  
                    double utemp = smoothed_U[j*width[i] + k];//get_average4(optical_field_U, height[i], width[i], j, k);  
                    double vtemp = smoothed_V[j*width[i] + k]; //get_average4(optical_field_V, height[i], width[i], j, k);  
                    double denominator = lamda + w*(Ix[j*width[i] + k] * Ix[j*width[i] + k]  
                        + Iy[j*width[i] + k] * Iy[j*width[i] + k]);  
                    double numerator = Ix[j*width[i] + k] * utemp + Iy[j*width[i] + k] *  
                        vtemp + It[j*width[i] + k];  
                      
                    optical_field_U[j*width[i] + k] = utemp - Ix[j*width[i] + k] *w*numerator / denominator;  
  
                    optical_field_V[j*width[i] + k] = vtemp - Iy[j*width[i] + k] *w*numerator / denominator;  
  
                    error += pow(optical_field_U[j*width[i] + k] - u_pre,2) +  
                        pow(optical_field_V[j*width[i] + k] - v_pre,2);  
                }  
  
            //下一次迭代前重新平滑光流场  
            if (error >exp(double(max_pyramid_layer-i))*precision&&iteration < maxiteration)  
            {  
                lowpass_filter(optical_field_U, height[i], width[i], smoothed_U);  
                lowpass_filter(optical_field_V, height[i], width[i], smoothed_V);  
            }  
        }  
        delete[]smoothed_U, smoothed_V,Ix,Iy,It;  
  
        if (i == 0)//得到最终光流场  
        {  
            return;  
        }  
  
        //下一层的光流场  
        double*new_of_u = new double[width[i - 1] * height[i - 1]];  
        double*new_of_v = new double[width[i - 1] * height[i - 1]];  
  
  
        //上采样  
        pyramid_up(optical_field_U, width[i], height[i], new_of_u, width[i - 1], height[i - 1]);  
        pyramid_up(optical_field_V, width[i], height[i], new_of_v, width[i - 1], height[i - 1]);  
        //将每个像素的光流按缩放因子放大,得到下一层的光流场的初值  
        //double scale = double(height[i - 1]) / height[i];  
        for (int j = 0; j < height[i - 1]; j++)  
            for (int k = 0; k < width[i - 1]; k++)  
            {  
                new_of_u[j*width[i - 1] + k] *= scale_factor;  
                new_of_v[j*width[i - 1] + k] *= scale_factor;  
            }  
        delete[]optical_field_U, optical_field_V;  
        optical_field_U = new_of_u;  
        optical_field_V = new_of_v;  
    }  
}  
  
//上采样,采用双线性插值,用双立方插值应该更精确  
void HornSchunckTracker::pyramid_up(double*src, int srcW, int srcH, double*dst, int dstW, int dstH)  
{  
    bilinear(src, dst, srcW, srcH, dstH, dstW);  
}  
  
//双线性插值  
void  HornSchunckTracker::bilinear(double* lpSrc, double* lpDst, int nW, int nH, int H1, int W1)  
{  
    float fw = float(nW) / W1;  
    float fh = float(nH) / H1;  
    int y1, y2, x1, x2, x0, y0;  
    float fx1, fx2, fy1, fy2;  
  
    for (int i = 0; i < H1; i++)  
    {  
        y0 = i*fh;  
        y1 = int(y0);  
        if (y1 == nH - 1)    y2 = y1;  
        else y2 = y1 + 1;  
  
        fy1 = y1 - y0;  
        fy2 = 1.0f - fy1;  
        for (int j = 0; j < W1; j++)  
        {  
            x0 = j*fw;  
            x1 = int(x0);  
            if (x1 == nW - 1)    x2 = x1;  
            else x2 = x1 + 1;  
  
            fx1 = y1 - y0;  
            fx2 = 1.0f - fx1;  
  
            float s1 = fx1*fy1;  
            float s2 = fx2*fy1;  
            float s3 = fx2*fy2;  
            float s4 = fx1*fy2;  
  
            double c1, c2, c3, c4;  
            c1 = lpSrc[y1*nW + x1];  
            c2 = lpSrc[y1*nW + x2];  
            c3 = lpSrc[y2*nW + x1];  
            c4 = lpSrc[y2*nW + x2];  
  
            double r;  
            r = (c1*s3) + (c2*s4) + (c3*s2) + (c4*s1);  
            lpDst[i*W1 + j] = r;  
        }  
    }  
}  
//双线性插值  
void  HornSchunckTracker::bilinear(BYTE* lpSrc, BYTE* lpDst, int nW, int nH, int H1, int W1)  
{  
    float fw = float(nW) / W1;  
    float fh = float(nH) / H1;  
    int y1, y2, x1, x2, x0, y0;  
    float fx1, fx2, fy1, fy2;  
  
    for (int i = 0; i < H1; i++)  
    {  
        y0 = i*fh;  
        y1 = int(y0);  
        if (y1 == nH - 1)    y2 = y1;  
        else y2 = y1 + 1;  
  
        fy1 = y1 - y0;  
        fy2 = 1.0f - fy1;  
        for (int j = 0; j < W1; j++)  
        {  
            x0 = j*fw;  
            x1 = int(x0);  
            if (x1 == nW - 1)    x2 = x1;  
            else x2 = x1 + 1;  
  
            fx1 = y1 - y0;  
            fx2 = 1.0f - fx1;  
  
            float s1 = fx1*fy1;  
            float s2 = fx2*fy1;  
            float s3 = fx2*fy2;  
            float s4 = fx1*fy2;  
  
            double c1, c2, c3, c4;  
            c1 = lpSrc[y1*nW + x1];  
            c2 = lpSrc[y1*nW + x2];  
            c3 = lpSrc[y2*nW + x1];  
            c4 = lpSrc[y2*nW + x2];  
  
            double r;  
            r = (c1*s3) + (c2*s4) + (c3*s2) + (c4*s1);  
            lpDst[i*W1 + j] = BYTE(r);  
        }  
    }  
}  
  
void HornSchunckTracker::set_paras(double lamda, double threshold, double scalefactor)  
{  
    this->lamda = lamda;  
    this->threshold = threshold;  
    scale_factor = scalefactor;  
}  
  
  
  
//double HornSchunckTracker::get_average4(double*src, const int height, const int width, const int i, const int j)  
//{  
//  if (j < 0 || j >= width) return 0;  
//  if (i < 0 || i >= height) return 0;  
//  
//  double val = 0.0;  
//  int tmp = 0;  
//  if ((i - 1) >= 0){  
//      ++tmp;  
//      val += src[(i - 1)*width + j];  
//  }  
//  if (i + 1<height){  
//      ++tmp;  
//      val += src[(i + 1)*width + j];  
//  }  
//  if (j - 1 >= 0){  
//      ++tmp;  
//      val += src[i*width + j - 1];  
//  }  
//  if (j + 1<width){  
//      ++tmp;  
//      val += src[i*width + j + 1];  
//  }  
//  return val / tmp;  
//  
//}  
  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
MATLAB 中可以使用光流法Horn-Schunck 光流法)来估计图像序列中的光流。下面是一种实现光流法的简单方法: 1. 读取图像序列:使用 MATLAB 的 imread 函数读取一系列图像,组成图像序列。可以将这些图像存储在一个矩阵中,每一帧图像作为矩阵的一个元素。 2. 灰度化:使用 MATLAB 的 rgb2gray 函数将图像序列转换为灰度图像序列。这是因为光流法对灰度图像效果更好。 3. 计算光流:使用 MATLAB 的 vision.OpticalFlow 光流估计对象来计算光流。创建一个光流估计对象,然后使用其 estimateFlow 计算方法进行计算。可以选择不同的光流算法,这里选择 Horn-Schunck 算法。 ```matlab % 创建光流估计对象 opticalFlow = vision.OpticalFlow('Method', 'Horn-Schunck'); % 依次对每一帧图像计算光流 for i = 2:size(graySequence, 4) flow = estimateFlow(opticalFlow, graySequence(:, :, :, i)); % 在图像上绘制光流向量 imshow(graySequence(:, :, :, i)); hold on plot(flow, 'ScaleFactor', 10); hold off drawnow; end ``` 4. 可视化光流:使用 MATLAB 的 plot 函数在图像上绘制光流向量,以便可视化光流的方向和强度。这些光流向量可用于分析图像序列中的运动。 上述代码中的 graySequence 表示灰度图像序列的矩阵,第一个维度表示图像的高度,第二个维度表示图像的宽度,第三个维度表示通道数(在灰度图像中为1),第四个维度表示帧数。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值