视讯稳定

本文详细阐述了视频稳定处理的全过程,包括光流场计算、变换累积、轨迹平滑和新变换应用,旨在平滑视频全局轨迹并减少稳定过程中的可见边界。通过使用滑动平均窗口对轨迹进行平滑,确保视频在平稳移动场景下保持流畅,同时减少因快速平移导致的不稳定性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

视讯稳定的原始的code的地址是:http://nghiaho.com/?p=2093

感觉视讯稳定里面最主要的code 是warpAfiine 的函数:

warpAffine 函数的实现是:
http://blog.youkuaiyun.com/fengbingchun/article/details/17713429

  1. Find the transformation from previous to current frame using optical flow for all frames. The transformation only consists of three parameters: dx, dy, da (angle). Basically, a rigid Euclidean transform, no scaling, no sharing。找到之前到现在帧的变换,对于每帧图片我们求这个图片的光流场,我的理解是如果求光流的话,一定是两张图片,我们从一张图片引线到另一张图片,变换包含三个参数:dx,dy,da(angle(角度?)),基本上,一个刚体的欧式变换,没有尺度变换,没有sharing(不明白啥意思)?
  2. Accumulate the transformations to get the “trajectory(轨迹)” for x, y, angle, at each frame.累加变换目的是得到每一帧的x,y,angle的轨迹。
  3. Smooth out the trajectory using a sliding average window. The user defines the window radius, where the radius is the number of frames used for smoothing.(利用一个平均滤波器来平滑轨迹,用户可以定义窗口的半径,半径的用来平滑帧数的数量,这意思是半径越大,用越多的帧来平滑这一帧?)
  4. Create a new transformation such that new_transformation = transformation + (smoothed_trajectory – trajectory).(构造一个新的变换,新变换=老的变换+ (smoothed_trajectory – trajectory)
  5. Apply the new transformation to the video. 应用新变换到这个视频里面。
#include <opencv2/opencv.hpp>
#include <iostream>
#include <cassert>
#include <cmath>
#include <fstream>

using namespace std;
using namespace cv;

// This video stablisation smooths the global trajectory using a sliding average window

const int SMOOTHING_RADIUS = 30; // In frames. The larger the more stable the video, but less reactive to sudden panning
const int HORIZONTAL_BORDER_CROP = 20; // In pixels. Crops the border to reduce the black borders from stabilisation being too noticeable.

// 1. Get previous to current frame transformation (dx, dy, da) for all frames
// 2. Accumulate the transformations to get the image trajectory
// 3. Smooth out the trajectory using an averaging window
// 4. Generate new set of previous to current transform, such that the trajectory ends up being the same as the smoothed trajectory
// 5. Apply the new transformation to the video

struct TransformParam
{
    TransformParam() {}
    TransformParam(double _dx, double _dy, double _da) {
        dx = _dx;
        dy = _dy;
        da = _da;
    }

    double dx;
    double dy;
    double da; // angle
};

struct Trajectory
{
    Trajectory() {}
    Trajectory(double _x, double _y, double _a) {
        x = _x;
        y = _y;
        a = _a;
    }

    double x;
    double y;
    double a; // angle
};

int main(int argc, char **argv)
{
    if(argc < 2) {
        cout << "./VideoStab [video.avi]" << endl;
        return 0;
    }

    // For further analysis
    ofstream out_transform("prev_to_cur_transformation.txt");
    ofstream out_trajectory("trajectory.txt");
    ofstream out_smoothed_trajectory("smoothed_trajectory.txt");
    ofstream out_new_transform("new_prev_to_cur_transformation.txt");

    VideoCapture cap(argv[1]);
    assert(cap.isOpened());

    Mat cur, cur_grey;
    Mat prev, prev_grey;

    cap >> prev;
    cvtColor(prev, prev_grey, COLOR_BGR2GRAY);

    // Step 1 - Get previous to current frame transformation (dx, dy, da) for all frames
    vector <TransformParam> prev_to_cur_transform; // previous to current

    int k=1;
    int max_frames = cap.get(CV_CAP_PROP_FRAME_COUNT);
    Mat last_T;

    while(true) {
        cap >> cur;

        if(cur.data == NULL) {
            break;
        }

        cvtColor(cur, cur_grey, COLOR_BGR2GRAY);// 把彩色图片转成灰度图片

        // vector from prev to cur
        vector <Point2f> prev_corner, cur_corner;
        vector <Point2f> prev_corner2, cur_corner2;
        vector <uchar> status;
        vector <float> err;

        goodFeaturesToTrack(prev_grey, prev_corner, 200, 0.01, 30);
        goodFeaturesToTrack  的函数定义:
               http://baike.baidu.com/link?url=TLaHlExF3xwZ3TEggi-aSRMmhv5kRKWbh1otteVQaf6WELDSZrj6wtU8M2YZnW1MR4eK4zf0ez091c9Bzy4xn_
        calcOpticalFlowPyrLK(prev_grey, cur_grey, prev_corner, cur_corner, status, err);
        计算一个稀疏特征集的光流,使用金字塔中的迭代 Lucas-Kanade 方法
     对于
http://baike.baidu.com/link?url=a1RXdSuq0rY33ncnLl2ITqFShZIjIpKNgSsLjmDEfaacA8olj79edwGAikFloyUbgssfJpJiNSaeKsfrdb3Ozq
// weed out bad matches for(size_t i=0; i < status.size(); i++) { if(status[i]) { prev_corner2.push_back(prev_corner[i]); cur_corner2.push_back(cur_corner[i]); } } // translation + rotation only Mat T = estimateRigidTransform(prev_corner2, cur_corner2, false); // false = rigid transform, no scaling/shearing // in rare cases no transform is found. We'll just use the last known good transform. if(T.data == NULL) { last_T.copyTo(T); } T.copyTo(last_T); // decompose T double dx = T.at<double>(0,2); double dy = T.at<double>(1,2); double da = atan2(T.at<double>(1,0), T.at<double>(0,0)); prev_to_cur_transform.push_back(TransformParam(dx, dy, da)); out_transform << k << " " << dx << " " << dy << " " << da << endl; cur.copyTo(prev); cur_grey.copyTo(prev_grey); cout << "Frame: " << k << "/" << max_frames << " - good optical flow: " << prev_corner2.size() << endl; k++; } // Step 2 - Accumulate the transformations to get the image trajectory // Accumulated frame to frame transform double a = 0; double x = 0; double y = 0; vector <Trajectory> trajectory; // trajectory at all frames for(size_t i=0; i < prev_to_cur_transform.size(); i++) { x += prev_to_cur_transform[i].dx; y += prev_to_cur_transform[i].dy; a += prev_to_cur_transform[i].da; trajectory.push_back(Trajectory(x,y,a)); out_trajectory << (i+1) << " " << x << " " << y << " " << a << endl; } // Step 3 - Smooth out the trajectory using an averaging window vector <Trajectory> smoothed_trajectory; // trajectory at all frames for(size_t i=0; i < trajectory.size(); i++) { double sum_x = 0; double sum_y = 0; double sum_a = 0; int count = 0; for(int j=-SMOOTHING_RADIUS; j <= SMOOTHING_RADIUS; j++) { if(i+j >= 0 && i+j < trajectory.size()) { sum_x += trajectory[i+j].x; sum_y += trajectory[i+j].y; sum_a += trajectory[i+j].a; count++; } } double avg_a = sum_a / count; double avg_x = sum_x / count; double avg_y = sum_y / count; smoothed_trajectory.push_back(Trajectory(avg_x, avg_y, avg_a)); out_smoothed_trajectory << (i+1) << " " << avg_x << " " << avg_y << " " << avg_a << endl; } // Step 4 - Generate new set of previous to current transform, such that the trajectory ends up being the same as the smoothed trajectory vector <TransformParam> new_prev_to_cur_transform; // Accumulated frame to frame transform a = 0; x = 0; y = 0; for(size_t i=0; i < prev_to_cur_transform.size(); i++) { x += prev_to_cur_transform[i].dx; y += prev_to_cur_transform[i].dy; a += prev_to_cur_transform[i].da; // target - current double diff_x = smoothed_trajectory[i].x - x; double diff_y = smoothed_trajectory[i].y - y; double diff_a = smoothed_trajectory[i].a - a; double dx = prev_to_cur_transform[i].dx + diff_x; double dy = prev_to_cur_transform[i].dy + diff_y; double da = prev_to_cur_transform[i].da + diff_a; new_prev_to_cur_transform.push_back(TransformParam(dx, dy, da)); out_new_transform << (i+1) << " " << dx << " " << dy << " " << da << endl; } // Step 5 - Apply the new transformation to the video cap.set(CV_CAP_PROP_POS_FRAMES, 0); Mat T(2,3,CV_64F); int vert_border = HORIZONTAL_BORDER_CROP * prev.rows / prev.cols; // get the aspect ratio correct k=0; while(k < max_frames-1) { // don't process the very last frame, no valid transform cap >> cur; if(cur.data == NULL) { break; } T.at<double>(0,0) = cos(new_prev_to_cur_transform[k].da); T.at<double>(0,1) = -sin(new_prev_to_cur_transform[k].da); T.at<double>(1,0) = sin(new_prev_to_cur_transform[k].da); T.at<double>(1,1) = cos(new_prev_to_cur_transform[k].da); T.at<double>(0,2) = new_prev_to_cur_transform[k].dx; T.at<double>(1,2) = new_prev_to_cur_transform[k].dy; Mat cur2; warpAffine(cur, cur2, T, cur.size());
        // 此处没有说明是使用的最近邻算法,还是bicubic的插值算法。

        cur2 = cur2(Range(vert_border, cur2.rows-vert_border), Range(HORIZONTAL_BORDER_CROP, cur2.cols-HORIZONTAL_BORDER_CROP));

        // Resize cur2 back to cur size, for better side by side comparison
        resize(cur2, cur2, cur.size());

        // Now draw the original and stablised side by side for coolness
        Mat canvas = Mat::zeros(cur.rows, cur.cols*2+10, cur.type());

        cur.copyTo(canvas(Range::all(), Range(0, cur2.cols)));
        cur2.copyTo(canvas(Range::all(), Range(cur2.cols+10, cur2.cols*2+10)));

        // If too big to fit on the screen, then scale it down by 2, hopefully it'll fit :)
        if(canvas.cols > 1920) {
            resize(canvas, canvas, Size(canvas.cols/2, canvas.rows/2));
        }

        imshow("before and after", canvas);

        //char str[256];
        //sprintf(str, "images/%08d.jpg", k);
        //imwrite(str, canvas);

        waitKey(20);

        k++;
    }

    return 0;
}
把每个函数都做一遍实验看是什么意思?



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值