继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

Python中的动态可视化讲解

繁星点点滴滴
关注TA
已关注
手记 375
粉丝 67
获赞 333
如何使用OpenCV和Matplotlib制作动画图表

实时追踪球的运动轨迹,并以动画图形式展示其垂直位置。

计算机视觉领域,一个基本目标是从静态图像或视频序列中提取有意义的信息,以达到这个目标。为了更好地理解这些信号,通常很有帮助的是将它们可视化

例如,在跟踪高速公路上的每一辆车时,我们可以用边框标注它们,或者在检测传送带上产品线中的异常时,我们可以用不同的颜色标记异常。但如果提取的信息呈现出更数值化的特征,并且你想要可视化该信号的时间变化呢?

仅仅显示数值可能不足以让你深入了解,特别是在信号变化迅速时。在这种情况下,用一个带有时间轴的图表来可视化这样的信号是一个很好的方法。在这篇文章里,我会展示如何结合使用OpenCV和Matplotlib来创建此类信号的实时动画可视化。

我在GitHub上分享了这个项目的代码和视频。

使用OpenCV追踪一个球并使用Matplotlib绘制轨迹 - GitHub:trflorian/ball-tracking-live-plot?source=post_page-----d640462c41f4--------------------------------
一个物体运动轨迹的绘制

让我们来看一个简单的例子,我拍了一段球垂直向上抛的视频。目标是跟踪视频中的球,并绘制其位置 p(t)、速度 v(t) 和加速度 a(t) 随时间的变动。

视频输入

让我们设参照系为相机,并为了简化,我们仅追踪图像中球的垂直位置变化。我们期望位置呈抛物线,速度线性递减,加速度恒定不变。

图形的草图

球分割

首先,我们需要在视频序列的每一帧中识别出球。因为相机是固定的,所以可以利用背景减除模型轻松检测球,并结合颜色模型去除帧中手的干扰。

首先,我们使用来自OpenCVVideoCapture简单地循环播放这个视频片段。视频播放结束后,我们再从头开始。我们还通过计算视频的FPS来确定sleep_time(以毫秒为单位),确保视频以原始帧速率播放,这样我们就能保持视频播放的流畅性。最后,记得释放资源并关闭窗口。

# 该脚本用于播放视频文件并允许通过按键'q'退出

import cv2  

cap = cv2.VideoCapture("ball.mp4")  
fps = int(cap.get(cv2.CAP_PROP_FPS))  

while True:  
    ret, frame = cap.read()  
    # 每次循环读取一帧
    if not ret:  
        cap.set(cv2.CAP_PROP_POS_FRAMES, 0)  
        continue  

    cv2.imshow("Frame", frame)  

    sleep_time = 1000 // fps  
    key = cv2.waitKey(sleep_time) & 0xFF  
    # 等待按键,0xFF表示取低8位
    if key == ord('q'):  
        break  

cap.release()  
cv2.destroyAllWindows()

视频输入的,可视化

首先,我们来提取球的二值分割掩码。这实际上就是我们要创建一个掩码,让球的像素为有效,而其他像素为无效。为了做到这一点,我将结合两个掩码,一个是运动掩码,另一个是颜色掩码。运动掩码主要提取出移动的部分,而颜色掩码则主要是为了去除帧中的手。

为了颜色滤镜,我们可以将图像转换为即HSV颜色空间,并选择一个特定的色调区间(20-100),这个区间包含球的绿色但避免了肤色的色调。我没有对饱和度或亮度值做限制,所以我们可以使用整个范围(0-255)。

    # 根据颜色筛选
    hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)  
    # 创建一个基于HSV颜色空间的掩码,范围从(20, 0, 0)到(100, 255, 255)
    mask_color = cv2.inRange(hsv, (20, 0, 0), (100, 255, 255))

要创建运动掩模,我们可以使用简单的背景减法模型。我们用视频的第一帧作为背景,并将学习率设为1。在循环中,我们应用背景模型得到前景掩膜,并通过将学习率设为0来防止新帧被整合进模型中。

    ...

    # 初始化背景模型,以便后续处理  
    bg_sub = cv2.createBackgroundSubtractorMOG2(varThreshold=50, detectShadows=False)  
    ret, frame0 = cap.read()  
    if not ret:  
        print("无法读取视频文件,可能是文件损坏或路径错误")  
        exit(1)  
    bg_sub.apply(frame0, learningRate=1.0)  # 以快速学习模式应用背景模型

    while True:  
      ...  
      # 根据运动过滤前景  
      mask_fg = bg_sub.apply(frame, learningRate=0)  # 逐帧处理,根据运动过滤前景

在下一步,我们可以合并两个掩模,并应用开形态学操作来去除小噪声,最终得到完美的球分割。

    # 将颜色掩码和前景掩码合并
    mask = cv2.bitwise_and(mask_color, mask_fg)
    mask = cv2.morphologyEx(
        mask, cv2.MORPH_OPEN, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (13, 13))
    )
    # 进行开运算以去除小的噪点

左上:视频帧序列、右上:彩色掩膜、左下:运动掩膜、右下:合成掩膜

追着球的轨迹

我们只剩下球在面具里了。首先,我提取球的轮廓,然后使用它的外接矩形中心来追踪球的中心。如果有噪声穿透了我们的滤镜,我会基于大小过滤出的轮廓只关注最大的那个。

    # 找我们要跟踪的球的最大轮廓
    contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    if len(contours) > 0:
        largest_contour = max(contours, key=cv2.contourArea)
        x, y, w, h = cv2.boundingRect(largest_contour)
        center = (x + w // 2, y + h // 2)
        # 这里的center是指球心的位置

我们也可以在框架上添加一些注释来更直观地展示我们的检测结果。我打算画两个圆,一个表示球心,一个表示球的边缘。

cv2.circle(frame, center, 30, (255, 0, 0), 2)  
cv2.circle(frame, center, 2, (255, 0, 0), 2)

在这幅图像的中心画一个半径为30像素的红色圆圈,并在相同中心画一个半径为2像素的红色圆圈。

为了跟踪球的位置,我们可以使用一个列表(list)。每次检测到球时,我们只需将中心点添加到列表中。我们也可以通过在跟踪位置列表中的每个位置之间画线来可视化轨迹。

    tracked_pos = []  

    while True:  
      ...  

      if len(contours) > 0:  
        ...   
        tracked_pos.append(center)  

      # 绘制追踪轨迹  
      for i in range(1, len(tracked_pos)):  
          cv2.line(frame, tracked_pos[i - 1], tracked_pos[i], (255, 0, 0), 1)

球轨迹可视化

构建情节

现在我们可以追踪到球了,让我们开始探索如何使用matplotlib来绘制信号。首先,我们可以在视频结束时创建最终的图表,然后再分第二步考虑实现如何实时动画。为了展示位置、速度和加速度这些信息,我们可以使用三个并排的子图。

    fig, axs = plt.subplots(nrows=1, ncols=3, figsize=(10, 2), dpi=100)  

    axs[0].set_title('位置')  
    axs[0].set_ylim(0, 700)  
    axs[1].set_title('速度')  
    axs[1].set_ylim(-200, 200)  
    axs[2].set_title('加速度')  
    axs[2].set_ylim(-30, 10)  

    for ax in axs:  
        ax.set_xlim(0, 20)  
        ax.grid(True)

我们只关心图像中的y位置(数组索引1),为了得到零偏移的位置图,我们可以从第一个位置开始减去。

pos0 = tracked_pos[0][1]  
pos = np.array([pos0 - pos[1] for pos in tracked_pos])

翻译为:

pos0 = tracked_pos中的第一个位置的第二个元素
pos = np.array([构造一个数组,其中每个元素是pos0减去tracked_pos中相应位置的第二个元素])

我们可以用位置的差值来近似速度,用速度的差值来近似加速度。

计算速度差分 `vel = np.diff(pos)` 和加速度差分 `acc = np.diff(vel)`

现在我们就可以把这些值画出来啦:

axs[0].plot(range(len(pos)), pos, c="b")  
axs[1].plot(range(len(vel)), vel, c="b")  
axs[2].plot(range(len(acc)), acc, c="b")  

plt.show()

位置、速度和加速度的静态图表

让情节生动起来

现在到了有趣的部分,我们想让这个图动起来!由于我们在OpenCV的GUI循环中工作,无法直接使用matplotlibshow函数,因为这会阻塞循环,使程序无法继续运行。相反,我们需要一些小技巧来实现✨

主要思路是将内存中的绘图绘制到缓存中,然后在我们的OpenCV窗口中显示这个缓存。通过手动调用画布的绘制函数,我们可以强制将图形渲染到缓存中。然后我们可以获取这个缓存并将其转换为数组。由于缓存是RGB格式,而OpenCV使用的是BGR格式,因此我们需要将颜色顺序从RGB调整为OpenCV使用的BGR格式。

fig.canvas.draw()  

buf = fig.canvas.buffer_rgba()  
plot = np.asarray(buf)  
plot = cv2.cvtColor(plot, cv2.COLOR_RGB2BGR)

请确保将 axs.plot 调用已经放在帧循环里:

    while True:  
      ...  

      axs[0].plot(range(len(pos)), pos, c="b")  # 绘制位置数据
      axs[1].plot(range(len(vel)), vel, c="b")  # 绘制速度数据
      axs[2].plot(range(len(acc)), acc, c="b")  # 绘制加速度数据  

      ... 

现在我们就可以用OpenCV的imshow函数显示图。

    cv2.imshow("Plot", plot)  # 显示名为“Plot”的图像

动画图

就这样,你的动画图就出来了!但是你会发现性能相当低。每一帧都重新绘制整个图非常耗时。为了提升性能,我们需要采用blitting。这是一项高级渲染技术,它把静态部分绘制到背景图像上,只重新绘制那些变化的部分。为了设置这个,我们需要在帧循环之前为每个图定义引用。

    pl_pos = axs[0].plot([], [], c="b")[0]  # 在第一个子图中绘制位置图线,颜色为蓝色
    pl_vel = axs[1].plot([], [], c="b")[0]  # 在第二个子图中绘制速度图线,颜色为蓝色
    pl_acc = axs[2].plot([], [], c="b")[0]  # 在第三个子图中绘制加速度图线,颜色为蓝色

我们先要在循环开始之前先绘制一次图形的轴背景,并分别获取每个轴的轴背景。

    fig.canvas.draw()  # 绘制图形
    bg_axs = [fig.canvas.copy_from_bbox(ax.bbox) for ax in axs]  # 从每个子图的边界框复制背景

在循环中,我们现在可以更改每个图的数据内容,对于每个子图,我们首先恢复每个子图背景区域,然后绘制新的图,最后调用blit函数来应用这些更改。

    # 更新绘图数据  
    pl_pos.set_data(range(len(pos)), pos)  
    pl_vel.set_data(range(len(vel)), vel)  
    pl_acc.set_data(range(len(acc)), acc)  

    # 更新位置图  
    fig.canvas.restore_region(bg_axs[0])  
    axs[0].draw_artist(pl_pos)  
    fig.canvas.blit(axs[0].bbox)  

    # 更新速度图  
    fig.canvas.restore_region(bg_axs[1])  
    axs[1].draw_artist(pl_vel)  
    fig.canvas.blit(axs[1].bbox)  

    # 更新加速度图  
    fig.canvas.restore_region(bg_axs[2])  
    axs[2].draw_artist(pl_acc)  
    fig.canvas.blit(axs[2].bbox)

于是,绘图速度加快了,性能也大幅提升了很多。

优化图

结论部分

在这篇文章中,你学会了如何应用简单的计算机视觉技术来学习如何应用简单的计算机视觉技术,以提取移动的前景对象,并追踪它的运动轨迹。然后,我们通过一个玩具视频演示了绘图,视频中一个球垂直抛向空中。然而,本项目中使用的工具和技术对于各种任务和实际应用都非常有用!在我的 GitHub 上可以找到完整的源代码。希望你今天有所收获,祝你编程愉快,身体健康!

使用OpenCV追踪球,并用Matplotlib绘制轨迹:GitHub: 使用OpenCV追踪球并使用Matplotlib绘制轨迹 - trflorian/ball-tracking-live-plot

(https://github.com/trflorian/ball-tracking-live-plot?source=post_page-----d640462c41f4--------------------------------)

本文中的所有图形均由作者制作。

打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP