基于opencv对高空拍摄视频消抖处理_opencv 视频防抖_js君的博客-程序员秘密

技术标签: 视频消抖  python  视觉处理  无人机拍摄  视频稳像  opencv  

一、问题背景

无人机在拍摄视频时,由于风向等影响因素,不可避免会出现位移和旋转,导致拍摄出的画面存在平移和旋转的帧间变换, 即“抖动” 抖动会改变目标物体 (车辆、行人) 的坐标,给后续的检测、跟踪任务引入额外误差,造成数据集不可用。

原效果
目标效果

理想的无抖动视频中,对应于真实世界同一位置的背景点在不同帧中的坐标应保持一致,从而使车辆、行人等目标物体的坐标变化只由物体本身的运动导致,而不包含相机的运动 抖动可以由不同帧中对应背景点的坐标变换来描述

二、量化指标

抖动可以用相邻帧之间的 x 方向平移像素 dx,y 方向平移像素 dy,旋转角度 da,缩放比例 s 来描述,分别绘制出 4 个折线图,根据折线图的走势可以判断抖动的程度 理想的无抖动视频中,dx、dy、da 几乎始终为 0,s 几乎始终为 1。

三、技术思路

我们最终实现,将视频的所有帧都对齐到第一帧,以达到视频消抖问题,实现逻辑如下图所示。

 (1)首先对视频进行抽第一帧与最后一帧,为什么抽取两帧?这样做的主要目的是,我们在做帧对齐时,使用帧中静态物的关键点做对齐,如果特征点来源于动态物上,那么对齐后就会产生形变,我们选取第一帧与最后一帧,提取特征点,留下交集部分,则可以得到静态特征点我们这里称为特征模板,然后将特征模板应用到每一帧上,这样可以做有效对齐。

(2)常用特征点检测器:

SIFT: 04 年提出,广泛应用于各种跟踪和识别算法,表现能力强,但计算复杂度高。

SURF: 06 年提出,是 SIFT 的演进版本,保持强表现能力的同时大大减少了计算量。

BRISK: BRIEF 的演进版本,压缩了特征的表示,提高了匹配速度。 ORB: 以速度著称,是 SURF 的演进版本,多用于实时应用。

GFTT: 最早提出的 Harris 角点的改进版本,经常合称为 Harris-Shi-Tomasi 角点。

SimpleBlob: 使用 blob 的概念来抽取图像中的特征点,相对于角点的一种创新。 FAST: 相比其他方法特征点数量最多,但也容易得到距离过近的点,需要经过 NMS。

Star: 最初用于视觉测距,后来也成为一种通用的特征点检测方法。

我们这里使用的是SURF特征点检测器

第一帧特特征点提取​​​​​​

最后一帧特征点提取

(3)在上图中,我们发现所提取的特征点中部分来自于车身,由于车是运动的,所以我们不能使用,我们用第一帧与最后一帧做静态特帧点匹配,生成静态特征模板,在下图中,我们发现只有所有的特征点只选取在静态物上

静态特征点模板

(4)静态特征模板匹配 ,我们这里使用Flann算法,匹配结果如下

特征匹配

(5)使用匹配成功的两组特征点,估计两帧之间的透视变换 (Perspective Transformation)。估计矩阵 H,其中 (x_i, y_i) 和 (x_i^′, y_i^′) 分别是两帧的特征点。

第一帧

最后一帧对齐到第一帧

四、实现代码

运行环境以及版本,安装命令如下:
python版本:3.X
opencv-python:3.4.2.16
opencv-contrib-python:3.4.2.16

需要卸载之前的opencv-python版本
pip uninstall opencv-python
pip uninstall opencv-contrib-python

安装新的版本
pip install opencv_python==3.4.2.16 
pip install opencv-contrib-python==3.4.2.16

代码基于python实现,如下所示:

import cv2
import numpy as np
from tqdm import tqdm
import argparse
import os

# get param
parser = argparse.ArgumentParser(description='')
parser.add_argument('-v', type=str, default='')  # 指定输入视频路径位置(参数必选)
parser.add_argument('-o', type=str, default='')  # 指定输出视频路径位置(参数必选)
parser.add_argument('-n', type=int, default=-1)  # 指定处理的帧数(参数可选), 不设置使用视频实际帧

# eg: python3 stable.py -v=video/01.mp4 -o=video/01_stable.mp4 -n=100 -p=6

args = parser.parse_args()

input_path = args.v
output_path = args.o
number = args.n

class Stable:
    # 处理视频文件路径
    __input_path = None

    __output_path = None

    __number = number

    # surf 特征提取
    __surf = {
        # surf算法
        'surf': None,
        # 提取的特征点
        'kp': None,
        # 描述符
        'des': None,
        # 过滤后的特征模板
        'template_kp': None
    }

    # capture
    __capture = {
        # 捕捉器
        'cap': None,
        # 视频大小
        'size': None,
        # 视频总帧
        'frame_count': None,
        # 视频帧率
        'fps': None,
        # 视频
        'video': None,
    }

    # 配置
    __config = {
        # 要保留的最佳特征的数量
        'key_point_count': 5000,
        # Flann特征匹配
        'index_params': dict(algorithm=0, trees=5),
        'search_params': dict(checks=50),
        'ratio': 0.5,
    }

    # 特征提取列表
    __surf_list = []

    def __init__(self):
        pass

    # 初始化capture
    def __init_capture(self):
        self.__capture['cap'] = cv2.VideoCapture(self.__video_path)
        self.__capture['size'] = (int(self.__capture['cap'].get(cv2.CAP_PROP_FRAME_WIDTH)),
                                  int(self.__capture['cap'].get(cv2.CAP_PROP_FRAME_HEIGHT)))

        self.__capture['fps'] = self.__capture['cap'].get(cv2.CAP_PROP_FPS)

        self.__capture['video'] = cv2.VideoWriter(self.__output_path, cv2.VideoWriter_fourcc(*"mp4v"),
                                                  self.__capture['fps'], self.__capture['size'])

        self.__capture['frame_count'] = int(self.__capture['cap'].get(cv2.CAP_PROP_FRAME_COUNT))

        if number == -1:
            self.__number = self.__capture['frame_count']
        else:
            self.__number = min(self.__number, self.__capture['frame_count'])

    # 初始化surf
    def __init_surf(self):

        self.__capture['cap'].set(cv2.CAP_PROP_POS_FRAMES, 0)
        state, first_frame = self.__capture['cap'].read()

        self.__capture['cap'].set(cv2.CAP_PROP_POS_FRAMES, self.__capture['frame_count'] - 1)
        state, last_frame = self.__capture['cap'].read()

        self.__surf['surf'] = cv2.xfeatures2d.SURF_create(self.__config['key_point_count'])

        self.__surf['kp'], self.__surf['des'] = self.__surf['surf'].detectAndCompute(first_frame, None)
        kp, des = self.__surf['surf'].detectAndCompute(last_frame, None)

        # 快速临近匹配
        flann = cv2.FlannBasedMatcher(self.__config['index_params'], self.__config['search_params'])
        matches = flann.knnMatch(self.__surf['des'], des, k=2)

        good_match = []
        for m, n in matches:
            if m.distance < self.__config['ratio'] * n.distance:
                good_match.append(m)

        self.__surf['template_kp'] = []
        for f in good_match:
            self.__surf['template_kp'].append(self.__surf['kp'][f.queryIdx])

    # 释放
    def __release(self):
        self.__capture['video'].release()
        self.__capture['cap'].release()

    # 处理
    def __process(self):

        current_frame = 1

        self.__capture['cap'].set(cv2.CAP_PROP_POS_FRAMES, 0)

        process_bar = tqdm(self.__number, position=current_frame)

        while current_frame <= self.__number:
            # 抽帧
            success, frame = self.__capture['cap'].read()

            if not success: return

            # 计算
            frame = self.detect_compute(frame)

            # 写帧
            self.__capture['video'].write(frame)

            current_frame += 1

            process_bar.update(1)

    # 视频稳像
    def stable(self, input_path, output_path, number):
        self.__video_path = input_path
        self.__output_path = output_path
        self.__number = number

        self.__init_capture()
        self.__init_surf()
        self.__process()
        self.__release()

    # 特征点提取
    def detect_compute(self, frame):

        frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

        # 计算特征点
        kp, des = self.__surf['surf'].detectAndCompute(frame_gray, None)

        # 快速临近匹配
        flann = cv2.FlannBasedMatcher(self.__config['index_params'], self.__config['search_params'])
        matches = flann.knnMatch(self.__surf['des'], des, k=2)

        # 计算单应性矩阵
        good_match = []
        for m, n in matches:
            if m.distance < self.__config['ratio'] * n.distance:
                good_match.append(m)

        # 特征模版过滤
        p1, p2 = [], []
        for f in good_match:
            if self.__surf['kp'][f.queryIdx] in self.__surf['template_kp']:
                p1.append(self.__surf['kp'][f.queryIdx].pt)
                p2.append(kp[f.trainIdx].pt)

        # 单应性矩阵
        H, _ = cv2.findHomography(np.float32(p2), np.float32(p1), cv2.RHO)

        # 透视变换
        output_frame = cv2.warpPerspective(frame, H, self.__capture['size'], borderMode=cv2.BORDER_REPLICATE)

        return output_frame


if __name__ == '__main__':

    if not os.path.exists(input_path):
        print(f'[ERROR] File "{input_path}" not found')
        exit(0)
    else:
        print(f'[INFO] Video "{input_path}" stable begin')

    s = Stable()
    s.stable(input_path, output_path, number)

    print('[INFO] Done.')
    exit(0)

参数说明:

-v    指定输入视频路径位置(参数必选)

-o    指定输出视频路径位置(参数必选)

-n    指定处理的帧数(参数可选), 不设置使用视频实际帧

调用示例:

python3 stable.py -v=test.mp4 -o=test_stable.mp4

五、效果展示

我们消抖后的视频道路完全没有晃动,但是在边界有马赛克一样的东西,那是因为图片对齐后后出现黑边,我们采用边缘点重复来弥补黑边。

消抖前

消抖后

 六、效率优化

目前的处理效率(原视频尺寸3840*2160),我们可以看出主要时间是花费在特征点(key)提取上。
可以采用异步处理+GPU提高计算效率

处理效率

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_43532890/article/details/114523558

智能推荐

cordova 建立工程生成apk_weixin_33911824的博客-程序员秘密

为什么80%的码农都做不了架构师?&gt;&gt;&gt; ...

hikariconfig mysql_关于Mysql8.0版本驱动getTables返回所有库的表问题浅析_独角瘦的博客-程序员秘密

前言本文主要介绍的是关于Mysql8.0驱动getTables返回所有库的表的相关内容,MySQL Connector/J 8.0版本驱动向下兼容之前的5.5+版本MySQL,如果你使用的是5.5+版本MySQL,都可以升级成8.0版本驱动。如果你是使用的5.X版本驱动,需要将Driver Class换成: com.mysql.cj.jdbc.Driver需要注意的是:8.0版本驱动DataSou...

Python爬虫selenium提取数据之driver对象常用方法_driver.page_source_bfhonor的博客-程序员秘密

selenium提取数据知识点:1. driver对象的常用属性和方法在使用selenium过程中,实例化driver对象后,driver对象有一些常用的属性和方法driver.page_source 当前标签页浏览器渲染之后的网页源代码driver.current_url 当前标签页的urldriver.close() 关闭当前标签页,如果只有一个标签

Flutter 必备开源项目推荐_flutter项目_CodeTitan的博客-程序员秘密

这段时间内一直学习Flutter, 自行设计完成了一个实战项目mahua_pet项目中用到了flutter中的大部分的组件, 界面也涉及了很多功能(可能很多地方还有待完善)项目目前也还是处在开发待完成阶段, 主要内容差不多已经完成下面是一些UI界面展示, 后面推见一些比较好的开源项目验证码登录和密码登录都可以登录, 账号: 123 密码: 123部分界面展示首页动态的展示导航栏的显示和隐藏日历模块展示日历的记录, 每日记录生成图片和保存图片发现类似朋友圈的内容展示, 图片浏览和.

name ‘train_test_split‘ is not defined解决方法-程序员秘密

name 'train_test_split' is not defined解决方法

controller中使用了@Async不起作用[email protected] controller_yxh13521338301的博客-程序员秘密

源于蚂蚁课堂的学习,点击这里查看(老余很给力)问题描述王德发?!本来想装逼使用@Async去提升代码执行效率,结果装逼失败,现在都脸疼,根本尼玛没生效啊。程序依旧是自上而下执行了,难受的一比,马哥!@Async底层原理@Async会使得当前controller类采用代理设计模式生成代理对象!!!你妹啊!!这当然会失效啊,代理模式中目标方法执行时,其方法体中调用本类其他方法,会使用this。而this指的就是目标对象,而非代理对象,所以,这时候还异步个毛啊!!都特么不走.

随便推点

前端面试精华帖:100个问题如果你回答出80个,那么请把你的简历给我_盏离的博客-程序员秘密

半年时间,几千人参与,精选大厂前端面试高频 100 题,这就是「壹题」。在 2019 年 1 月 21 日这天,「壹题」项目正式开始,在这之后每个工作日都会出一道高频面试题,主要涵盖阿里、腾讯、头条、百度、网易等大公司和常见题型。得益于大家热情参与,现在每道题都有很多答案,提供的解题思路和答案也大大增长了我的见识,到现在已累积 100 道题目,『8000+』Star 了,可...

WebSphere下用JNDI_astro_boy的博客-程序员秘密

自己遇到过的问题,贴出来让和我遇到同样问题的人参考一下远程客户端测试代码Properties properties =new   Properties(); properties.setProperty(&quot;java.naming.factory.initial&quot;, &quot;com.ibm.websphere.naming.WsnInitialContextFactory&quot;);下面代码中红色的部分...

全局loading加载_J橘子的博客-程序员秘密

全局加载(loading)初始化全局loading状态let loading = null封装loading显示与隐藏的方法//显示加载function showLoading(){ if(requestCount === 0){ loading = Message({ message:"加载中...", duration:0 })}//隐藏加载function hideLoading(){ if(loadin

深圳连讯达支持福禄克FLUKE DTX-1800以旧换新DSX2-8000与DSX2-5000活动_深圳连讯达的博客-程序员秘密

设备升级,就在此刻我放观点:FLUKE DTX-1800是我非常钟爱的一款设备,性能稳定,测试带宽大,如果你不愿意换,我也表示理解,毕竟谁的钱都不是大风刮来的。如果要换的理由是什么?我们看看下面的理由。提问:为什么一定要升级换代?从 1994 年第一款铜缆测试仪 DSP 诞生,到 2004 年 DSP 下市, DTX 系列推出,再到 2015 年 DTX 系列全面下线,DSX 系列成为主流,经历了 21 年推陈出新。无论从技术上还是服务上,旧型号都无法满足当下的要求。(...

【数据聚类】第四章第一节3:DBSCAN性能分析、优缺点和参数选择方法_怎么评价dbscan分类器的性能_快乐江湖的博客-程序员秘密

DBSCAN算法对数据集中的每个点都要检索其邻域内的所有点,时间复杂度为O(n2)O(n^{2})O(n2)。但在低纬空间中,若采用kkk-ddd树、RRR树等结构,可以有效地检索特定点给定距离内的所有点,其时间复杂度可以降低到O(nlogn)O(nlogn)O(nlogn)①:可以对任意形状的稠密数据集进行聚类②:可以在聚类的同时发现异常点,对数据集中的异常点不敏感③:不需要指定簇数,并且多次实验结果往往是相同的①:如果样本集的密度不均匀、聚类间距差相差很大时,聚类质量较差,这时用DBSCAN聚类一般不适

java.lang.NoClassDefFoundError: org/apache/ibatis/session/SqlSessionFactory_翱翔天地的博客-程序员秘密

1、错误描述org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'processEngineConfiguration' defined in class path resource [activiti.cfg.xml]: Initialization of bean fail

推荐文章

热门文章

相关标签