跳到主要内容

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

快速使用

  1. 创建一个项目文件夹

    mkdir opencv-mobile-test
    cd opencv-mobile-test
  2. 下载 opencv-mobile luckfox-pico 预编译包,在虚拟机中解压

    opencv-mobile-4.8.1-luckfox-pico.zip

    unzip opencv-mobile-4.8.1-luckfox-pico.zip
  3. 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})
  4. 源代码

    创建文件:

    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;
    }
  5. 编译

    执行命令:

    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
  6. 项目目录结构

    opencv-mobile-test/             # 项目根目录
    ├── build # 编译输出目录
    ├── CMakeLists.txt # 项目 CMake 配置文件
    ├── main.cpp # 项目源代码文件
    └── opencv-mobile-4.8.1-luckfox-pico/ # opencv-mobile 库目录
  7. 传输文件

    scp opencv-mobile-test root@开发板ip地址:/root
  8. 运行程序

    开发板连接摄像头后上电,启动后释放摄像头资源:

    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.简要说明

  1. opencv-mobile highgui 模块实现基于 v4l2 访问摄像头流
  2. 在运行时自动动态加载 rkaiq 库实现 ISP 图像调节
  3. 在运行时自动动态加载 rga 库实现 YUV2BGR 硬件加速
  4. 无需修改代码,调用 cv::VideoCapture 便自动支持,支持设置分辨率
  5. 因为只测试验证了 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: