跳到主要内容

OpenCV 开发案例

1. OpenCV 简介​

OpenCV 是开源的计算机视觉库,常用于读取图片和视频、转换色彩、去噪、提取轮廓和分析画面变化。在 Python 中,一张图片或一帧视频通常以 NumPy 数组表示。可以先处理单张图片,再把同样的操作应用到视频的每一帧。

本篇从图片和视频两个场景入手。图片案例先用高斯滤波减少噪声,再用 Canny 检测轮廓。视频案例可以切换灰度预览、边缘预览和运动区域预览。运动区域通过比较相邻帧的亮度来标出画面变化,适合观察固定机位下的活动。

在 Rockchip 板卡上,系统 GStreamer 调用 MPP 硬件解码本地视频,Python 端的 OpenCV 负责处理解码后的帧并在 HDMI 桌面显示。后文先演示读取灰度帧,再介绍如何直接读取 NV12 的亮度平面,并对比两种路径的板端预览速率。

2. 环境准备​

2.1 适用版型与本次实测环境​

本次使用 Python OpenCV 5.0.0 实测了以下两套环境:

板卡系统与桌面内核Python
ArmSoM CM5 + CM5-IO(RK3576)Debian 12 arm64 XFCE6.1.118-rk35763.11
ArmSoM AIM7(RK3588)Ubuntu 22.04.5 LTS arm64 GNOME/X116.1.118-rk35883.10.12

下表列出 SDK 提供的板型与系统配置。其余板型和镜像组合需按第 2.2 节确认依赖,并在目标板端验证。

ArmSoM SDK 的多个板型共用 Debian 和 Ubuntu rootfs 构建目录。SDK 中可找到以下 ArmSoM 产品配置。

芯片板型SDK 系统配置
RK3528Sige1Debian
RK3576AIM5、CM5(CM5-IO 和 CM4 Nano B IO)、Sige5Debian、Ubuntu
RK3588 / RK3588SAIM7、CM7S、LM7、Sige7、Sige7SDebian、Ubuntu

SDK 的 Debian 12、Ubuntu 22.04 和 Ubuntu 24.04 rootfs 目录均包含 ARM64 的 gst-rkmpp 软件包。表格列出的是 SDK 配置。运行 HDMI 案例时,请使用桌面镜像,确认 gst-inspect-1.0 mppvideodec 能找到插件且本地显示可用。静态图片案例只需要 Python OpenCV 和输入图片。

2.2 安装与检查依赖​

  • 使用对应板型的桌面镜像,已验证的系统版本见第 2.1 节。
  • 将显示器接到 HDMI 并启动图形桌面。串口运行时需指定可访问的桌面显示,实测为 :0。
  • 准备 Python venv、FFmpeg(含 ffprobe 和 libx264)及系统 GStreamer。镜像需提供 mppvideodec、qtdemux、h264parse 和 videoconvert。如果无法找到 mppvideodec,请先检查镜像是否包含 Rockchip MPP 插件。
  • 在独立环境安装带 GUI 的 OpenCV 包。若已有相同版本的环境,直接复用。SDK 源码和系统 OpenCV 无需调整。

首次启动后,接好 HDMI,登录桌面,再接网线或在桌面连接 Wi-Fi。以下命令在板端终端执行。串口可用于输入命令,预览窗口仍显示在板端桌面。APT 命令以 root 身份执行,普通用户请在 APT 命令前加 sudo。

apt update
apt install -y python3-venv curl ffmpeg x11-xserver-utils gstreamer1.0-tools python3-gi \
gir1.2-gstreamer-1.0 gir1.2-gst-plugins-base-1.0

mkdir -p ~/cvproj
cd ~/cvproj
python3 -m venv venv
./venv/bin/python -m pip install --only-binary=:all: opencv-python==5.0.0.93
./venv/bin/python -c 'import cv2; print(cv2.__version__)'
# 检查 GStreamer 能否找到 Rockchip MPP 硬件解码器。
gst-inspect-1.0 mppvideodec
for element in qtdemux h264parse videoconvert appsink; do
gst-inspect-1.0 "$element" >/dev/null || break
done
DISPLAY=:0 xrandr --current

确认 OpenCV 输出 5.0.0,gst-inspect-1.0 mppvideodec 能显示 Factory Details 或 Plugin Details(此命令只查询插件信息),其他元件检查无报错,且 xrandr 显示 HDMI 已连接。若缺少 MPP 或其他 GStreamer 元件,请检查当前板型的官方桌面镜像和对应软件包,再继续测试。串口遇到无法连接显示器的错误时,请在 HDMI 桌面的终端运行,或使用当前桌面会话的 DISPLAY 值和登录用户。

2.3 刚刷镜像时的 APT 排查​

软件源查看、备份和更新的通用步骤见更新 APT 软件源。AIM7 的 Ubuntu 镜像实测遇到了以下两种情况:

  • 提示 Release file ... is not valid yet。先执行 timedatectl status 对照当前日期、时间和时区。实测板端时钟落后约一个月。通过桌面设置启用自动校时,或在无时间同步服务时手动设为当前时间,再重新运行 apt update。串口手动校时时,可以用 date -s '当前日期 时间 时区偏移',执行前请替换引号内的全部内容。
  • 更新卡在镜像附带的第三方源。确认 /etc/apt/sources.list 包含与当前系统匹配的官方 Ubuntu 源后,本次可临时跳过 /etc/apt/sources.list.d/。下面的命令以 root 身份执行,分别刷新索引和安装缺失的 python3-venv。其他依赖有缺失时,可用同样的参数执行第 2.2 节的安装命令。
apt-get -o Dir::Etc::sourceparts=- -o Acquire::Retries=0 -o Acquire::http::Timeout=20 update
apt-get -o Dir::Etc::sourceparts=- install -y python3-venv

这些选项只对本次命令生效,软件源文件无需修改。

3. 完整 Python 示例​

将 mpp_preview.py 保存到板端的 ~/cvproj/,或把下方完整代码复制到同名文件。执行 cd ~/cvproj && ./venv/bin/python mpp_preview.py --help,确认脚本和 Python 环境均可用。

#!/usr/bin/env python3
"""Preview local H.264/H.265 MP4 video via Rockchip MPP and OpenCV."""
import argparse
import json
import os
from pathlib import Path
import subprocess
import time

os.environ.setdefault("DISPLAY", ":0")
os.environ.setdefault("QT_XCB_GL_INTEGRATION", "none")

import cv2
import numpy as np


def preview(path):
probe = subprocess.check_output([
"ffprobe", "-v", "error", "-select_streams", "v:0",
"-show_entries", "stream=codec_name,width,height,avg_frame_rate",
"-of", "json", str(path)
])
stream = json.loads(probe)["streams"][0]
parser = {"h264": "h264parse", "hevc": "h265parse"}.get(stream["codec_name"])
if parser is None:
raise ValueError("Expected H.264 or H.265 video in an MP4/MOV container")
w, h = stream["width"], stream["height"]
num, den = map(int, stream["avg_frame_rate"].split("/"))
delay = max(1, round(1000 * den / num)) if num and den else 33
stride = (w + 3) & ~3
cmd = [
"gst-launch-1.0", "-q", "filesrc", f"location={path}", "!",
"qtdemux", "!", parser, "!", "mppvideodec", "!", "videoconvert",
"!", f"video/x-raw,format=GRAY8,width={w},height={h}",
"!", "fdsink", "fd=1", "sync=false"
]
title = "RK3576 MPP - grayscale"
frames = 0
start = time.monotonic()
stopped = False
with subprocess.Popen(cmd, stdout=subprocess.PIPE) as decoder:
try:
while True:
raw = decoder.stdout.read(stride * h)
if not raw:
break
if len(raw) != stride * h:
raise RuntimeError("Incomplete grayscale frame")
gray = np.frombuffer(raw, np.uint8).reshape(h, stride)[:, :w]
cv2.imshow(title, gray)
frames += 1
if cv2.waitKey(delay) & 255 in (27, ord("q")):
stopped = True
break
if not stopped and decoder.wait(timeout=5):
raise RuntimeError("MPP pipeline failed")
finally:
if decoder.poll() is None:
decoder.kill()
cv2.destroyAllWindows()
if not frames:
raise RuntimeError("MPP produced no video frames")
elapsed = time.monotonic() - start
print(f"elapsed={elapsed:.3f}s preview_fps={frames / elapsed:.2f}")
print(f"OpenCV {cv2.__version__} | mppvideodec | {w}x{h} | frames={frames}")


if __name__ == "__main__":
args = argparse.ArgumentParser(description=__doc__)
args.add_argument("video", type=Path, help="Local H.264/H.265 MP4 or MOV path")
path = args.parse_args().video.resolve()
if not path.is_file():
args.error(f"File not found: {path}")
try:
preview(path)
except (OSError, ValueError, IndexError, RuntimeError, subprocess.SubprocessError, cv2.error) as exc:
args.exit(1, f"Preview failed: {exc}\n")

4. 使用 1080p 短片测试​

下载 30 秒的全高清样片,从第 12 秒截取 8 秒。已有 H.264 MP4 时,可直接把本地路径传给脚本。按 q 或 Esc 可提前退出。

cd ~/cvproj
mkdir -p samples
curl -fL https://raw.githubusercontent.com/renesas-rz/media/main/Big_Buck_Bunny/big_buck_bunny_1080p_30fps_30s.mp4 \
-o samples/bbb-source.mp4
ffmpeg -nostdin -ss 12 -i samples/bbb-source.mp4 -t 8 -an \
-c:v libx264 -preset ultrafast -pix_fmt yuv420p samples/bbb-1080p.mp4
DISPLAY=:0 ./venv/bin/python mpp_preview.py samples/bbb-1080p.mp4
echo "TEST_EXIT=$?"

素材为 Blender Foundation 的 Big Buck Bunny(© 2008,CC BY 3.0)。截图使用测试样片的节选,并转换为灰度。

保持窗口打开直到视频播完。使用本节样片时,终端应打印 frames=240 和 TEST_EXIT=0。确认基础预览可用后,按第 7.2 节下载优化脚本,再用第 7.3 节命令验证三种模式。

下载的原视频保存在 ~/cvproj/samples/bbb-source.mp4,截取的 1080p 测试短片保存在 ~/cvproj/samples/bbb-1080p.mp4。在板端桌面终端运行 xdg-open ~/cvproj/samples/bbb-1080p.mp4 可查看彩色短片。灰度、边缘和运动区域效果由示例窗口实时显示,脚本不另存处理后的视频。

5. 图片与视频边缘检测​

5.1 静态图片边缘检测​

从视频截取一帧,先用高斯滤波减少噪声,再用 Canny 提取边缘。输出图可用于查看物体轮廓和纹理边界。

ffmpeg -nostdin -ss 2 -i samples/bbb-1080p.mp4 -frames:v 1 samples/bbb-frame.jpg
./venv/bin/python - <<'PY'
import cv2

image = cv2.imread("samples/bbb-frame.jpg")
if image is None:
raise RuntimeError("Cannot read the sample frame")
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
edges = cv2.Canny(cv2.GaussianBlur(gray, (5, 5), 0), 60, 160)
if not cv2.imwrite("samples/bbb-edges.png", edges):
raise RuntimeError("Cannot save the edge image")
print("IMAGE_OK", edges.shape, cv2.countNonZero(edges))
PY

截取的原图保存在 ~/cvproj/samples/bbb-frame.jpg,生成的边缘图保存在 ~/cvproj/samples/bbb-edges.png。看到 IMAGE_OK 后,可以用 OpenCV 直接在 HDMI 桌面打开结果。将焦点切到图片窗口后,按任意键关闭。

cd ~/cvproj
DISPLAY=:0 QT_XCB_GL_INTEGRATION=none ./venv/bin/python - <<'PY'
import cv2

image = cv2.imread("samples/bbb-edges.png", cv2.IMREAD_GRAYSCALE)
if image is None:
raise SystemExit("Cannot read samples/bbb-edges.png")
cv2.namedWindow("Canny edges", cv2.WINDOW_NORMAL)
cv2.resizeWindow("Canny edges", 960, 540)
cv2.imshow("Canny edges", image)
cv2.waitKey(0)
cv2.destroyAllWindows()
PY

板端生成的 Canny 边缘图(缩小展示)

5.2 视频边缘预览​

下方是写入 mpp_preview.py 的 Python 代码片段。若只想运行视频边缘预览,先按第 7.2 节保存 NV12 脚本,然后在终端运行 cd ~/cvproj && ./venv/bin/python mpp_nv12_preview.py samples/bbb-1080p.mp4 --mode edges。

也可以逐帧预览边缘。把完整示例中的 cv2.imshow(title, gray) 替换为下面两行,保持原缩进。

edges = cv2.Canny(cv2.GaussianBlur(gray, (5, 5), 0), 60, 160)
cv2.imshow(title, edges)

5.3 视频运动区域​

在 frames = 0 后初始化 previous = None,再用下列代码替换原来的 cv2.imshow(title, gray)。白色表示相邻两帧中亮度变化超过阈值的区域。固定机位更容易观察运动物体,镜头移动时背景也会被标亮。

smooth = cv2.GaussianBlur(gray, (5, 5), 0)
if previous is None:
mask = np.zeros_like(smooth)
else:
diff = cv2.absdiff(smooth, previous)
_, mask = cv2.threshold(diff, 25, 255, cv2.THRESH_BINARY)
previous = smooth
cv2.imshow(title, mask)

6. 板端实测结果​

6.1 板端复测步骤​

复测基础灰度预览,请按第 4 章操作。复测 NV12 灰度、边缘和运动区域预览,请按第 7 章操作。

6.2 实测截图与结果​

在搭载 CM5-IO 的 ArmSoM CM5(RK3576)上,使用 Debian 12 arm64 XFCE 和 OpenCV 5.0.0 实测。1920×1080 H.264 短片在灰度预览和边缘预览中均处理完 240 帧。图片案例成功保存了 1920×1080 边缘图。下图是从板端 HDMI 桌面正在运行的灰度预览窗口抓取的截图。

RK3576 OpenCV 1080p grayscale preview

素材为 Blender Foundation 的 Big Buck Bunny(© 2008,CC BY 3.0)。截图使用测试样片的节选,并转换为灰度。

OpenCV 5.0.0 | mppvideodec | 1920x1080 | frames=240
TEST_EXIT=0

6.3 预览速率实测​

HDMI-1 当前输出模式为 1920×1080、60 Hz,测试短片的输入帧率为 30 fps。下表用提交到 OpenCV 窗口的帧数除以程序耗时,包含解码、图像处理和窗口事件处理。

模式帧数耗时平均预览送帧速率
灰度预览24020.045 秒11.97 fps
运动区域预览24018.518 秒12.96 fps

两次测试均正常退出。表中是单次运行结果,不能据此判断两种模式的性能高低。60 Hz 是 HDMI 输出刷新率,程序送帧速率不等于显示器实际呈现的不同画面数。

7. 优化 1080p 预览到接近 30 fps​

7.1 定位瓶颈​

在 CM5 上通过 COM4 对同一段 1080p30 H.264 短片分段测试。每次只运行一条管线,输入均为 240 帧。分段测试以 fakesink sync=false 结束,不开窗口、不按播放时钟限速。

测试路径耗时平均速率
MPP 解码 → fakesink0.694 秒345.82 fps
MPP 解码 → videoconvert 转 GRAY8 → fakesink19.043 秒12.60 fps
原 GRAY8 管线 → Python(关闭窗口)20.076 秒11.95 fps
原 GRAY8 管线 → OpenCV 窗口20.045 秒11.97 fps
NV12 亮度平面 → OpenCV 窗口8.018 秒29.93 fps

主要耗时出现在本次镜像上 videoconvert 转 GRAY8 的路径。只把 waitKey 改成 1 毫秒,原尺寸窗口也只有 12.50 fps。把缩放移到管道前、以 960×540 预览,实测为 14.31 fps。

7.2 直接读取 NV12 亮度平面​

保留 MPP 的 NV12 输出,用进程内的 GStreamer appsink 取帧。NV12 的 Y 平面已经提供亮度数据,可以用 NumPy 按实际行步长读取,再交给 OpenCV 显示。板上测到有效宽度为 1920、步长为 1984 字节,直接按 1920 字节重排行会读错位置。脚本还根据视频帧率和单调时钟计算剩余等待时间。

将 mpp_nv12_preview.py 保存到 ~/cvproj/。该版用于 H.264 MP4,复用板端 OpenCV 5.0.0 虚拟环境,以及与其 Python 版本匹配的系统 PyGObject 和 GStreamer introspection 绑定。可先用以下命令检查绑定并运行:

cd ~/cvproj
python3 -c 'import gi; gi.require_version("Gst", "1.0"); gi.require_version("GstVideo", "1.0"); from gi.repository import Gst, GstVideo'
DISPLAY=:0 ./venv/bin/python mpp_nv12_preview.py samples/bbb-1080p.mp4
NV12 luma 1920 1080 stride 1984
frames=240 elapsed=8.018s fps=29.93

优化版保持 1920×1080 预览,完整处理 240 帧,退出码为 0。29.93 fps 是程序向窗口送帧的平均速率,HDMI 输出模式仍是 60 Hz。这个数值也包含播放节奏控制。改动只涉及 Python 和 GStreamer 管线。

7.3 边缘检测与运动区域实测​

同一份脚本可用 --mode gray、--mode edges 或 --mode motion 切换预览。边缘模式在 Y 平面上做高斯滤波和 Canny 检测。运动模式对相邻的滤波帧求绝对差,再用阈值 25 生成运动掩膜。三种模式共用 MPP 硬解和按源视频帧率控制的预览循环。

下载本节的更新版脚本后,在 CM5 的 HDMI 桌面运行以下命令。测试使用第 4 节生成的同一段 1080p30 短片。

cd ~/cvproj
for mode in gray edges motion; do
DISPLAY=:0 ./venv/bin/python mpp_nv12_preview.py \
samples/bbb-1080p.mp4 --mode "$mode"
done
模式原 GRAY8 管线NV12 管线帧数
灰度预览11.97 fps29.94 fps240
边缘检测13.29 fps29.93 fps240
运动区域12.59 fps29.94 fps240

三种 NV12 模式均正常退出。边缘检测和运动区域模式的耗时均为 8.017 秒(保留三位小数)。

表中是向 OpenCV 窗口提交的平均速率。NV12 版本按输入的 30 fps 节奏运行,因此这些数字反映预览表现,不能用于推算算法的最大吞吐量。灰度的 GRAY8 数据来自第 7.1 节。

7.4 CM5 与 AIM7 实测对比​

两块板卡均使用同一段 1920×1080、30 fps、240 帧的 H.264 视频和手册下载版脚本。AIM7 在 Ubuntu 22.04.5 GNOME/X11 上也成功生成并打开了 1080p Canny 边缘图。两块板卡的 HDMI 输出模式均为 1080p、60 Hz。

预览模式CM5 · Debian 12 XFCEAIM7 · Ubuntu 22.04 GNOME/X11
基础 GRAY8 灰度11.97 fps5.68 fps
NV12 灰度29.94 fps29.95 fps
NV12 边缘检测29.93 fps29.95 fps
NV12 运动区域29.94 fps29.96 fps

AIM7 的四种预览均处理完 240 帧,退出码为 0。数值为程序向窗口送帧的平均速率。NV12 版本按源视频节奏播放,两块板卡使用的系统和桌面也不同,因此这组数据用于说明各自实测配置下的预览效果。

联系我们