• day20 - 绘制物体的运动轨迹


    在我们平常做目标检测或者目标追踪时,经常要画出目标的轨迹图。绘制轨迹图的一种方法就是利用光流估计来进行绘制。

    本期我们主要来介绍视频中光流估计的使用和效果,利用光流估计来绘制运动轨迹。

    完成本期内容,你可以:

    • 掌握视频的读取与显示
    • 掌握光流估计的流程和原理
    • 掌握使用光流估计绘制运功轨迹的代码实现

    若要运行案例代码,你需要有:

    • 操作系统:Ubuntu 16.04

    • 工具软件:PyCharm 2020.1.5, Anaconda3 2020.07

    • 硬件环境:无特殊要求

    • 核心库:python 3.6.13, opencv-python 3.4.2.16

    点击下载源码


    视频的读取与显示

    (1) OpenCV中的函数 cv2.VideoCapture() 可以用来完成摄像头的初始化工作。其语法格式如下:

    dst = cv2.VideoCapture(index)
    
    • 1

    说明

    • index:摄像头ID号
    • 默认值:-1,表示随机选取一个摄像头。
    • 如果有多个摄像头,使用数字0表示第一个摄像头、使用数字1表示第2个摄像头,以此类推。
    • 如果只有一个摄像头,既可以使用数字0作为ID,也可以使用数字-1作为ID。
    • 在某些平台上,使用参数-1,会使OpenCV弹出一个选择窗口,让用户手动选择希望使用的摄像头。
    • 该函数同样适用于处理视频文件。

    (2) OpenCV中的函数 cv2.isOpened() 可以用来判断当前的摄像头是否初始化成功。其语法格式如下:

    retval = cv2.VideoCapture.isOpened()
    
    • 1

    说明

    • 判断当前的摄像头是否初始化成功,并根据摄像头初始化成功与否,返回不同的逻辑值retval:
    • 如果成功,该函数返回逻辑值真(True);
    • 如果不成功,该函数返回逻辑值假(False)。
    • 当函数返回值为逻辑值假时,说明摄像头初始化失败。这时,可以使用函数cv2.VideoCapture.open()打开摄像头。

    (3) OpenCV中的函数 cv2.VideoCapture.read()可以用来进行帧捕捉。其语法格式如下:

    retval, image=cv2.VideoCapture.read()
    
    • 1

    说明

    • image:是帧捕获的返回值,如果没有帧被捕获,则该值为空。
    • retval:用来表示捕获结果是否成功的逻辑值。如果成功返回逻辑值真(True);不成功返回逻辑值假(False)。

    (4) OpenCV中的函数 cv2.VideoCapture.release()可以用来进行释放摄像头。其语法格式如下:

    None=cv2.VideoCapture.release()
    
    • 1

    说明

    • 在不需要摄像头时,需要关闭摄像头。
    • 例如,当前有cv2.VideoCapture类的对象cap,则将其释放的语句为:cap.release()

    保存视频文件

    **(1)**OpenCV中的函数 cv2.VideoWriter() 可以用来创建类对象。其语法格式如下:

    <VideoWriter object> = cv2.VideoWriter( filename, fourcc, fps, frameSize)
    
    • 1

    参数说明

    • filename:保存视频的路径
    • fourcc:用4个字符表示的视频编码格式
    • fps:帧速率
    • frameSize:每一帧的大小

    **(2)**OpenCV中的函数 cv2.VideoWriter.write()可以用来进行帧捕捉。其语法格式如下:

    None=cv2.VideoWriter.write(image)
    
    • 1

    说明

    • image:要写入的视频帧
    • 通常情况下,要求彩色图像的格式为BGR模式。

    **(3)**OpenCV中的函数 cv2.VideoCapture.release()可以用来进行释放摄像头。其语法格式如下:

    None=cv2.VideoCapture.release()
    
    • 1

    说明

    • 在不需要VideoWriter类对象时,需要释放该对象。
    • 例如,当前有VideoWriter类的对象out,则将其释放的语句为:out.release()

    光流估计

    稀疏特征集光流

    Opencv中使用cv2.calcOpticalFlowPyrLK()函数计算一个稀疏特征集的光流其语法格式如下:

    nextPts,status,err = cv2.calcOpticalFlowPyrLK(prevImg,nextImg,prevPts,nextPts,[, status[, err[, winSize[, maxLevel[, criteria[, flags[, minEigThreshold]]]]]]])
    
    • 1

    参数说明

    • prevImg: 上一帧图片;
    • nextImg:当前帧图片;
    • prevPts:上一帧找到的特征点向量;
    • nextPts:与返回值中的nextPtrs相同;
    • status:与返回的status相同;
    • err:与返回的err相同;
    • winSize:在计算局部连续运动的窗口尺寸(在图像金字塔中),default=Size(21, 21);
    • maxLevel:图像金字塔层数,0表示不使用金字塔, default=3;
    • criteria:寻找光流迭代终止的条件;
    • flags:有两个宏,表示两种计算方法,分别是OPTFLOW_USE_INITIAL_FLOW表示使用估计值作为寻找到的初始光流,OPTFLOW_LK_GET_MIN_EIGENVALS表示使用最小特征值作为误差测量,default=0;
    • minEigThreshold:该算法计算光流方程的2×2规范化矩阵的最小特征值,除以窗口中的像素数; 如果此值小于minEigThreshold,则会过滤掉相应的功能并且不会处理该光流,因此它允许删除坏点并获得性能提升, default=1e-4.
    • nextPtrs:输出一个二维点的向量,这个向量可以是用来作为光流算法的输入特征点,也是光流算法在当前帧找到特征点的新位置(浮点数);
    • status:标志,在当前帧当中发现的特征点标志status==1,否则为0;
    • err:向量中的每个特征对应的错误率.
    稠密光流法

    Opencv的函数cv2.calcOpticalFlowFarneback寻找稠密光流。其语法格式如下:

    flow=cv.calcOpticalFlowFarneback(prev,next, flow,pyr_scale, levels, winsize,iterations, poly_n, poly_sigma, flags)
    
    • 1

    参数说明

    • prev:前一帧图片
    • next:下一帧图片,格式与prev相同
    • flow:与返回值相同,得到一个CV_32FC2格式的光流图
    • pyr_scale:构建图像金字塔尺度
    • levels:图像金字塔层数
    • winsize:窗口尺寸,值越大探测高速运动的物体越容易,但是越模糊,同时对噪声的容错性越强
    • iterations:对每层金字塔的迭代次数
    • poly_n:每个像素中找到多项式展开的邻域像素的大小。n越大越光滑,也越稳定
    • poly_sigma:高斯标准差,用来平滑倒数,n越大,sigma应该适当增加
    • flags:光流的方式,有OPTFLOW_USE_INITIAL_FLOW 和OPTFLOW_FARNEBACK_GAUSSIAN 两种
    • flow:一个两通道的光流向量,实际上是每个点的像素的位移值
    光流估计的流程

    光流估计的流程通常包含以下步骤:

    • 定义角点检测参数
    • 定义光流估计参数
    • 定义轨迹颜色
    • 灰度处理
    • 角点检测
    • 创建掩膜
    • 进入循环
      • 读取每一帧图像并转换为灰度图
      • 光流估计
      • 匹配后上升维度
      • 绘制轨迹
      • 显示
      • 更新

    具体步骤

    使用高通滤波提取图像边缘。

    步骤一:创建项目工具

    创建项目名为绘制物体的运动轨迹,项目根目录下新建code文件夹储存代码,新建dataset文件夹储存数据,项目结构如下:

    绘制物体的运动轨迹                        # 项目名称
    ├── code                               # 储存代码文件
    ├── dataset                            # 储存数据文件
    
    • 1
    • 2
    • 3

    注:如项目结构已存在,无需再创建。

    步骤二:读取视频

    1. 导入所需模块:OpenCV、NumPy ;
    2. 读取dataset文件夹下的dog.mp4 视频;

    代码实现

    import numpy as np
    import cv2 as cv
    # 读取视频
    cap = cv.VideoCapture('../dataset/dog.mp4')
    
    • 1
    • 2
    • 3
    • 4

    步骤三:定义角点参数

    代码实现

    # 设置ShiTomasi 角点检测的参数
    feature_params = dict( maxCorners = 100,
                           qualityLevel = 0.3,
                           minDistance = 7,
                           blockSize = 7 )
    
    • 1
    • 2
    • 3
    • 4
    • 5

    步骤四:定义光流估计的参数

    代码实现

    # 设置lucas kanade 光流的参数
    lk_params = dict( winSize  = (15,15),
                      maxLevel = 2,
                      criteria = (cv.TERM_CRITERIA_EPS | cv.TERM_CRITERIA_COUNT, 10, 0.03))
    # 创建一些随机颜色
    color = np.random.randint(0,255,(100,3))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    步骤五:角点检测、创建掩码图像

    1. 读取第一帧并转换为灰度图像
    2. 检测图像中的角点
    3. 创建掩码图像

    代码实现

    # 读取第一帧
    ret, old_frame = cap.read()
    old_gray = cv.cvtColor(old_frame, cv.COLOR_BGR2GRAY)
    # 跟踪检测图像中的角点
    p0 = cv.goodFeaturesToTrack(old_gray, mask = None, **feature_params)
    
    # 为绘图目的创建掩码图像
    mask = np.zeros_like(old_frame)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    步骤六:光流估计并绘制

    1. 读取每一帧图像并转为灰度图
    2. 光流估计
    3. 匹配后上升维度
    4. 绘制轨迹
    5. 显示轨迹
    6. 更新上一帧和之前的点

    代码实现

    while(1):
        ret,frame = cap.read()
        if frame is None:
            break
        frame_gray = cv.cvtColor(frame, cv.COLOR_BGR2GRAY)
    
        # 需要传入前一帧和当前图像以及前一帧检测到的角点
        p1, st, err = cv.calcOpticalFlowPyrLK(old_gray, frame_gray, p0, None, **lk_params)
    
        # 选择好的点,st=1表示找到特征点
        if p1 is not None:
            good_new = p1[st==1]
            good_old = p0[st==1]
    
        # 绘制轨迹
        for i,(new,old) in enumerate(zip(good_new, good_old)):
            a,b = new.ravel() #变成一维
            c,d = old.ravel()
            mask = cv.line(mask, (int(a),int(b)),(int(c),int(d)), color[i].tolist(), 2)
            frame = cv.circle(frame,(int(a),int(b)),5,color[i].tolist(),-1)
        img = cv.add(frame,mask)
    
        cv.imshow('frame',img)
        k = cv.waitKey(30) & 0xff
        if k == 27:
            break
    
        # 现在更新上一帧和之前的点
        old_gray = frame_gray.copy()
        p0 = good_new.reshape(-1,1,2)
    cv.destroyAllWindows()
    cap.release()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    请添加图片描述

    实验效果

    本期重点是使用稀疏光流进行图像运动轨迹的绘制,这对于目标检测或追踪有着重要的意义。

    点击下载源码

  • 相关阅读:
    C/C++/Python图像处理算法实战【3】彩色图像灰度化和二值化处理
    Mac 打开 MySQL
    安装sourcetree并操作Git
    C++ std::unique_lock 用法介绍
    产品经理想升职加薪?这个证书你考了吗?
    [4G/5G/6G专题基础-160]: 5G双链接与MCG/SCG/PCell/PSCell/SCell
    数据结构——查找
    把ipa文件上传到App Store教程步骤
    03_SpringBoot项目配置
    TensorFlow 2.9的零零碎碎(六)-模型训练和评价
  • 原文地址:https://blog.csdn.net/qq_40186237/article/details/130861807