opencv-mobile
本文将介绍 "opencv-mobile",一款体积仅有官方 1/10 的精简 OpenCV 库,以及它在 LuckFox Pico 平台上的应用。通过 luckfox-pico MIPI CSI 摄像头和 rkaiq/rga 硬件加速,opencv-mobile 实现了对摄像头流的访问、ISP 图像调节和硬件加速。
原文出处:opencv-mobile 现已支持 luckfox-pico MIPI CSI 摄像头和 rkaiq/rga 硬件加速
Github:https://github.com/nihui/opencv-mobilegithub.com/nihui/opencv-mobile
快速使用
创建一个项目文件夹
mkdir opencv-mobile-test
cd opencv-mobile-test下载 opencv-mobile luckfox-pico 预编译包,在虚拟机中解压
opencv-mobile-4.8.1-luckfox-pico.zip
unzip opencv-mobile-4.8.1-luckfox-pico.zip
cmake 设置
创建文件:
vi CMakeLists.txt
添加以下内容,将 <SDK Directory> 修改为自己的 SDK 路径,如
/home/luckfox/luckfox-pico/
project(opencv-mobile-test)
cmake_minimum_required(VERSION 3.5)
set(CMAKE_CXX_STANDARD 11)
SET(CMAKE_C_COMPILER "<SDK Directory>/tools/linux/toolchain/arm-rockchip830-linux-uclibcgnueabihf/bin/arm-rockchip830-linux-uclibcgnueabihf-gcc")
SET(CMAKE_CXX_COMPILER "<SDK Directory>/tools/linux/toolchain/arm-rockchip830-linux-uclibcgnueabihf/bin/arm-rockchip830-linux-uclibcgnueabihf-g++")
set(OpenCV_DIR "${CMAKE_CURRENT_SOURCE_DIR}/opencv-mobile-4.8.1-luckfox-pico/lib/cmake/opencv4")
find_package(OpenCV REQUIRED)
include_directories(${OpenCV_INCLUDE_DIRS})
add_executable(opencv-mobile-test main.cpp)
target_link_libraries(opencv-mobile-test ${OpenCV_LIBS})源代码
创建文件:
vi main.cpp
添加以下内容:
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <unistd.h> // sleep()
int main()
{
cv::VideoCapture cap;
cap.set(cv::CAP_PROP_FRAME_WIDTH, 320);
cap.set(cv::CAP_PROP_FRAME_HEIGHT, 240);
cap.open(0);
const int w = cap.get(cv::CAP_PROP_FRAME_WIDTH);
const int h = cap.get(cv::CAP_PROP_FRAME_HEIGHT);
fprintf(stderr, "%d x %d\n", w, h);
cv::Mat bgr[9];
for (int i = 0; i < 9; i++)
{
cap >> bgr[i];
sleep(1);
}
cap.release();
// combine into big image
{
cv::Mat out(h * 3, w * 3, CV_8UC3);
bgr[0].copyTo(out(cv::Rect(0, 0, w, h)));
bgr[1].copyTo(out(cv::Rect(w, 0, w, h)));
bgr[2].copyTo(out(cv::Rect(w * 2, 0, w, h)));
bgr[3].copyTo(out(cv::Rect(0, h, w, h)));
bgr[4].copyTo(out(cv::Rect(w, h, w, h)));
bgr[5].copyTo(out(cv::Rect(w * 2, h, w, h)));
bgr[6].copyTo(out(cv::Rect(0, h * 2, w, h)));
bgr[7].copyTo(out(cv::Rect(w, h * 2, w, h)));
bgr[8].copyTo(out(cv::Rect(w * 2, h * 2, w, h)));
cv::imwrite("out.jpg", out);
}
return 0;
}编译
执行命令:
mkdir build
cd build
cmake ..
make编译成功后得到可执行文件opencv-mobile-test
luckfox@luckfox:~/opencv-mobile-test/build$ ls
CMakeCache.txt CMakeFiles cmake_install.cmake Makefile opencv-mobile-test项目目录结构
opencv-mobile-test/ # 项目根目录
├── build # 编译输出目录
├── CMakeLists.txt # 项目 CMake 配置文件
├── main.cpp # 项目源代码文件
└── opencv-mobile-4.8.1-luckfox-pico/ # opencv-mobile 库目录传输文件
scp opencv-mobile-test root@开发板ip地址:/root
运行程序
开发板连接摄像头后上电,启动后释放摄像头资源:
killall rkipc
运行程序
# ./opencv-mobile-test
[19083.672366] stream_cif_mipi_id0: s_power 1, entity use_count 1
devpath = /dev/video11
driver = rkisp_v7
card = rkisp_mainpath
bus_info = platform:rkisp-vir0
version = 20000
capabilities = 84201000
device_caps = 4201000
fmt = UYVY 4:2:2 59565955
fmt = Y/CbCr 4:2:2 3631564e
fmt = Y/CrCb 4:2:2 3136564e
fmt = Y/CrCb 4:2:0 3132564e
size = 32 x 16 ~ 2304 x 1296 (+8 +8)
fmt = Y/CbCr 4:2:0 3231564e
fmt = Y/CrCb 4:2:0 (N-C) 31324d4e
fmt = Y/CbCr 4:2:0 (N-C) 32314d4e
rkaiq log level ff0
[19083.854380] stream_cif_mipi_id0: open video, entity use_countt 2
[19083.854602] stream_cif_mipi_id1: open video, entity use_countt 1
/dev/video11 does not support changing fps
rga_api version 1.9.1_[0]
[19083.869267] rkisp rkisp-vir0: first params buf queue
[19083.871403] rkisp_hw ffa00000.rkisp: set isp clk = 198000000Hz
[19083.871447] rkcif-mipi-lvds: sditf_reinit_mode, mode->rdbk_mode 0, mode->name rkisp-vir0, link_mode 1
[19083.873291] rkcif-mipi-lvds: strea320 x 240
m[0] start streaming
[19083.873461] rockchip-mipi-csi2 ffa20000.mipi-csi2: stream on, src_sd: 796325b9, sd_name:rockchip-csi2-dphy0
[19083.873476] rockchip-mipi-csi2 ffa20000.mipi-csi2: stream ON
[19083.873524] rockchip-csi2-dphy0: dphy0, data_rate_mbps 506
[19083.873554] rockchip-csi2-dphy csi2-dphy0: csi2_dphy_s_stream stream on:1, dphy0
[19083.873566] rockchip-csi2-dphy csi2-dphy0: csi2_dphy_s_stream stream on:1, dphy0
[19092.985051] rkcif-mipi-lvds: stream[0] start stopping, total mode 0x2, cur 0x2
[19093.008280] rockchip-mipi-csi2 ffa20000.mipi-csi2: stream off, src_sd: 796325b9, sd_name:rockchip-csi2-dphy0
[19093.008329] rMessageParser process loop exit!o
ckchip-mipi-csi2 ffa20000.mipi-csi2: stream OFF
[19093.010662] rockchip-csi2-dphy csi2-dphy0: csi2_dphy_s_stream_stop stream stop, dphy0
[19093.010721] rockchip-csmpp[3524]: mpp_buffer: mpi_buf_id = 136, dma_buf_fd = 5
mpp[3524]: mpp: mClinetFd 7 open ok attr.chan_id 0
i2-dphy csi2-dphy0: csi2_dphy_s_stream stream on:0, dphy0
[19093.010771] rockchip-csi2-dphy csi2-dphy0: csi2_dphy_s_stream stream on:0, dph# y0
[19093.010986] rkcif-mipi-lvds: stream[0] stopping finished, dma_en 0x0
[19093.024110] stream_cif_mipi_id1: close video, entity use_count 0
[19093.024207] stream_cif_mipi_id0: close video, entity use_count 1
[19093.024859] stream_cif_mipi_id0: s_power 0, entity use_count 0
[19093.043230] mpp_vcodec: 44: num_chan = 0
[19093.043341] mpp_vcodec: 103: chan_entry->handle f6d67a53, enc f6d67a53
[19093.044028] 755: MPP_ENC_SET_CFG in
[19093.044076] 524: MPP_ENC_SET_RC_CFG bps 0 [0 : 0] fps [1:1] gop 1成功运行后得到一张九张图拼接成的图片out.jpg
# ls
opencv-mobile-test out.jpg
以上是帮助新人在 LuckFox Pico 平台上快速使用 opencv-mobile 的详细步骤, 下面介绍原文的主要内容:
1.简要说明
- opencv-mobile highgui 模块实现基于 v4l2 访问摄像头流
- 在运行时自动动态加载 rkaiq 库实现 ISP 图像调节
- 在运行时自动动态加载 rga 库实现 YUV2BGR 硬件加速
- 无需修改代码,调用
cv::VideoCapture
便自动支持,支持设置分辨率 - 因为只测试验证了 luckfox-pico,白名单暂时只有 luckfox-pico
下载新版本 opencv-mobile luckfox-pico 预编译包
opencv-mobile-4.8.1-luckfox-pico.zip
解压到项目目录中,cmake 中设置 OpenCV_DIR 路径,find_package 找到
project(opencv-mobile-test)
cmake_minimum_required(VERSION 3.5)
set(CMAKE_CXX_STANDARD 11)
set(OpenCV_DIR "${CMAKE_CURRENT_SOURCE_DIR}/opencv-mobile-4.8.1-luckfox-pico/lib/cmake/opencv4")
find_package(OpenCV REQUIRED)
add_executable(opencv-mobile-test main.cpp)
target_link_libraries(opencv-mobile-test ${OpenCV_LIBS})
1.1 opencv-mobile
https://github.com/nihui/opencv-mobilegithub.com/nihui/opencv-mobile
opencv-mobile 通过调整编译参数,删减部分opencv源码,来最小化编译的 opencv 库
提供了 opencv 常用的功能,如读写图片,处理,矩阵操作等等,版本与上游同步,无第三方依赖
在绝大多数情况下,以 1/10 的体积无痛替换官方 opencv,尤其适合对体积有特殊要求的移动端和嵌入式环境
1.2 luckfox-pico
LuckFox Pico 系列是基于瑞芯微 RV1103/RV1106 芯片的低成本微型 linux 开发板。RV1103/RV1106 是用于AI应用的IPC视觉处理器SoC,单核ARM A7,内置NPU计算能力高达 0.5TOPs
1.3 v4l2
V4L2英文全称是Video for Linux2,它是专门为视频设备设计的内核驱动。在做视频的开发中,一般我们操控V4L2的设备节点就可以直接对摄像头进行操作。
1.4 rkaiq
RkAiq(Rockchip Automatical Image Quality)不断从ISP获取图像统计,结合IQ Tuning参数,使用一系列算法计算出新的ISP、Sensor等硬件参数,不断迭代该过程,最终达到最优的图像效果。
1.5 rga
RGA(Raster Graphic Acceleration Unit)是一个独立的2D硬件加速器,可用于加速点/线绘制,执行图像缩放、旋转、bitBlt、alpha混合等常见的2D图形操作。
2.一些实现细节和限制
2.1 加载特定版本的 librkaiq/librga
https://github.com/LuckfoxTECH/luckfox-picogithub.com/LuckfoxTECH/luckfox-pico
从luckfox-pico的sdk中可以看到 media/rga 有版本号,找到 gitlab 上对应版本源码
https://gitlab.com/rk3588_linux/linux/linux-rga/-/tree
和mpp库类似,rkaiq/rga也经常新增功能和修改接口用法,得用sdk中的特定版本才work
为了减少编译耦合,opencv-mobile中采用运行时 dlopen/dlsym 方式加载 librkaiq/librga,即便编译时候缺库依然兼容可用
2.2 白名单
优化代码在 luckfox-pico max 上做了验证测试,但是 rkaiq/rga 版本之间的不兼容,无法确保其他 rockchip 平台的可用性
加载aiq/rga库时,额外判断 /proc/device-tree/model
是否为 luckfox-pico 设备,在其他设备上则会打开摄像头失败
2.3 使用经ISP处理后的摄像头流
系统里有许多 /dev/videoN 设备,其中 /sys/class/video4linux/videoN/name
名为 rkisp_mainpath 的设备才是经过 ISP 处理优化后的
直接读 /dev/video0
会获得 Bayered 格式,是原始数据无法直接使用。而 /dev/video11
获得的是ISP处理后颜色亮度正常的图像
2.4 分辨率和帧率
测试中发现ISP处理对分辨率有对齐要求
- w 是 16 倍数
- h 是 2 倍数
如果用户指定输出分辨率,会结合对齐规则并尽可能维持比例向上补齐,在返回图像时再裁切到用户需要的分辨率
分辨率对帧率影响较大,1080p下明显受制于返回cv::Mat
的memcpy耗时,并且 v4l2 实际无法真正设置fps
2.5 v4l2 video buffer
在网上很多 v4l2 教程里都会提到用多个 video buffer 轮换使用的策略,这种策略比较适合摄像头主动push图像的情景
opencv 的接口通常是由用户主动pull图像,多个buffer容易导致用户pull到过去时间的图像,使用1个buffer能总是pull最新帧
2.6 rkaiq 的行为
rkaiq 接口属于相对黑盒,在没启用时会获得颜色不正确的图像,但只要 rkaiq sysctl_start
,从v4l2拿到的数据就是处理后的状态
rkaiq 在摄像头刚打开,需要几秒的图像统计,才能进行 ISP 画质调整,最开头的帧总是很黑
2.7 rga 只 work 在 dma buffer
rga 的输入可以是来自 v4l2 expbuf importbuffer_fd
但是测试 rga 输出直接对 cv::Mat
data importbuffer_virtualaddr
会直接卡住
无奈只能另外新开 dma buffer 用于存放 rga 转换结果,转换完成后再 memcpy 到 cv::Mat
。这个 dma buffer 可以一直复用
3.摄像头测试和保存帧图像
- 用
cv::VideoCapture
打开摄像头,设置分辨率320x240 - 每隔1秒获取1帧图像
- 关闭摄像头
- 最后把9张图拼接在一起保存 (
cv::imwrite
有rkmpp加速哦!) - 开头2帧因为rkaiq还在统计图像信息,来不及ISP自动处理,所以很黑
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <unistd.h> // sleep()
int main()
{
cv::VideoCapture cap;
cap.set(cv::CAP_PROP_FRAME_WIDTH, 320);
cap.set(cv::CAP_PROP_FRAME_HEIGHT, 240);
cap.open(0);
const int w = cap.get(cv::CAP_PROP_FRAME_WIDTH);
const int h = cap.get(cv::CAP_PROP_FRAME_HEIGHT);
fprintf(stderr, "%d x %d\n", w, h);
cv::Mat bgr[9];
for (int i = 0; i < 9; i++)
{
cap >> bgr[i];
sleep(1);
}
cap.release();
// combine into big image
{
cv::Mat out(h * 3, w * 3, CV_8UC3);
bgr[0].copyTo(out(cv::Rect(0, 0, w, h)));
bgr[1].copyTo(out(cv::Rect(w, 0, w, h)));
bgr[2].copyTo(out(cv::Rect(w * 2, 0, w, h)));
bgr[3].copyTo(out(cv::Rect(0, h, w, h)));
bgr[4].copyTo(out(cv::Rect(w, h, w, h)));
bgr[5].copyTo(out(cv::Rect(w * 2, h, w, h)));
bgr[6].copyTo(out(cv::Rect(0, h * 2, w, h)));
bgr[7].copyTo(out(cv::Rect(w, h * 2, w, h)));
bgr[8].copyTo(out(cv::Rect(w * 2, h * 2, w, h)));
cv::imwrite("out.jpg", out);
}
return 0;
}
运行后得到out.jpg: