第二章:ISP算法工程环境搭建

做ISP算法部署,第一关不是算法本身,而是把环境搭起来。我见过太多人算法写得飞起,一到嵌入式板子上就各种报错——说白了,就是交叉编译环境没搞对。这一章,咱们就把这些基础活儿做扎实。

2.1 交叉编译工具链配置

嵌入式设备资源有限,没法在上面直接编译代码。我们得在PC上编译好,再把二进制丢到板子上跑。这就是交叉编译。

2.1.1 工具链的选择

市面上常见的工具链有几种,我列个表,你一看就明白:

工具链类型 适用平台 个人评价
arm-linux-gnueabihf ARM Cortex-A系列 最通用,海思、瑞芯微都能用
aarch64-linux-gnu ARM 64位平台 现在新板子基本都是这个
mipsel-linux-gnu MIPS架构 老款芯片还在用,慢慢少了

我个人习惯用 arm-linux-gnueabihf。为什么?因为大部分安防芯片,比如海思的Hi3516系列、君正的T系列,都支持这个。你想想看,一套工具链能覆盖好几个项目,省事。

2.1.2 安装与验证

安装其实不复杂,解压、配环境变量就行。但有个坑——路径不能有中文和空格。我曾经有个同事,把工具链放在“下载 (2)”文件夹里,折腾了一下午没搞定。

标准步骤:

  1. 下载工具链压缩包,比如 gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf.tar.xz
  2. 解压到 /opt/ 目录:sudo tar -xvf gcc-linaro-*.tar.xz -C /opt/
  3. 配置环境变量,编辑 ~/.bashrc,添加:
export PATH=$PATH:/opt/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf/bin
export CROSS_COMPILE=arm-linux-gnueabihf-
export ARCH=arm
  1. 使生效:source ~/.bashrc
  2. 验证:arm-linux-gnueabihf-gcc --version
小技巧: 验证时如果提示“找不到命令”,别慌。先检查 /opt/ 下有没有 bin 目录,再确认 PATH 有没有写对。我每次配完都会 echo $PATH 看一眼。

2.2 OpenCV与V4L2集成

ISP算法离不开图像采集和处理。OpenCV做算法验证,V4L2做视频采集,这两个必须打通。

2.2.1 交叉编译OpenCV

OpenCV的交叉编译,嗯,这里要注意——千万别用apt-get安装。PC版和ARM版不通用,必须自己编译。

我整理了一份精简的编译脚本,你照着来:

#!/bin/bash
# 交叉编译OpenCV 4.5.5 for ARM

export TOOLCHAIN=/opt/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf
export CC=$TOOLCHAIN/bin/arm-linux-gnueabihf-gcc
export CXX=$TOOLCHAIN/bin/arm-linux-gnueabihf-g++

cmake -DCMAKE_SYSTEM_NAME=Linux \
      -DCMAKE_SYSTEM_PROCESSOR=arm \
      -DCMAKE_C_COMPILER=$CC \
      -DCMAKE_CXX_COMPILER=$CXX \
      -DCMAKE_INSTALL_PREFIX=./install \
      -DWITH_V4L=ON \
      -DWITH_LIBV4L=ON \
      -DBUILD_opencv_python=OFF \
      -DBUILD_SHARED_LIBS=ON \
      ..

make -j4
make install

这里有几个关键点:

  • WITH_V4L 和 WITH_LIBV4L 必须打开,否则没法从摄像头拿数据
  • BUILD_opencv_python 关掉,嵌入式板子一般不跑Python
  • BUILD_SHARED_LIBS 打开,生成动态库,节省固件空间
避坑指南: 我曾经编译OpenCV时忘了加 -DCMAKE_INSTALL_PREFIX,结果库文件散落在系统目录里,拷贝到板子上时缺了一堆依赖。后来我统一把输出放到 ./install 目录,打包时直接 tar 整个目录,干净利落。

2.2.2 V4L2采集流程

V4L2是Linux下视频采集的标准接口。说白了,就是打开设备、设置格式、申请缓冲区、开始采集。流程很固定:

  1. 打开设备:open("/dev/video0", O_RDWR)
  2. 查询能力:ioctl(fd, VIDIOC_QUERYCAP, &cap)
  3. 设置格式:ioctl(fd, VIDIOC_S_FMT, &fmt)
  4. 申请缓冲区:ioctl(fd, VIDIOC_REQBUFS, &req)
  5. 入队、出队循环:VIDIOC_QBUF / VIDIOC_DQBUF

代码示例(核心片段):

struct v4l2_format fmt = {0};
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = 1920;
fmt.fmt.pix.height = 1080;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_NV12;
fmt.fmt.pix.field = V4L2_FIELD_NONE;

if (ioctl(fd, VIDIOC_S_FMT, &fmt) < 0) {
    perror("设置格式失败");
    close(fd);
    return -1;
}

为什么用NV12格式?因为大部分ISP芯片的硬件编码器直接支持NV12,省去格式转换的开销。你想想看,一帧1080p的NV12数据只有1920×1080×1.5字节,比RGB24少一半,传输和处理都快得多。

2.3 嵌入式Linux系统准备

板子上的Linux系统,说白了就是内核、根文件系统、设备树这三件套。我们做ISP算法部署,不需要从头造轮子,但得知道怎么裁剪和配置。

2.3.1 内核配置要点

内核里跟ISP相关的驱动,必须编译进内核或者作为模块加载。我列个清单:

配置项 说明 建议
CONFIG_MEDIA_SUPPORT 多媒体框架支持 必须开启
CONFIG_V4L_PLATFORM_DRIVERS 平台V4L驱动 必须开启
CONFIG_VIDEO_ISP ISP驱动 按芯片开启
CONFIG_VIDEO_SENSOR sensor驱动 按型号开启

配置方法:

make ARCH=arm menuconfig
# 进入 Device Drivers -> Multimedia support
# 勾选对应的ISP和sensor驱动
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j4
核心经验: 我建议第一次编译时把驱动都编成模块(M),调试稳定后再编进内核(Y)。这样改驱动时只需要重新编译模块,不用整个内核重编,省时间。

2.3.2 根文件系统裁剪

安防设备存储空间有限,根文件系统不能太大。我一般控制在50MB以内。怎么做到?

  • 用BusyBox:替代GNU coreutils,一个命令才几十KB
  • 删掉man文档:嵌入式设备谁看手册?省掉几十MB
  • 只保留必要的库:用 arm-linux-gnueabihf-readelf -d 查看依赖,只拷贝用到的so

举个例子,一个最小化的根文件系统结构:

rootfs/
├── bin/          # BusyBox 和基本命令
├── dev/          # 设备节点
├── etc/          # 配置文件
├── lib/          # 动态库(glibc、libpthread等)
├── mnt/          # 挂载点
├── proc/         # procfs
├── sbin/         # 系统管理命令
├── tmp/          # 临时文件
└── usr/          # 用户程序(我们的ISP算法放这里)
    └── bin/
        └── isp_demo   # 编译好的ISP算法可执行文件

2.4 环境验证:跑通第一个ISP流水线

环境搭好没?跑个最简单的例子验证一下。从摄像头采集一帧图像,用OpenCV转成灰度图,再显示出来。虽然嵌入式板子没显示器,但我们可以把结果保存成文件。

#include <opencv2/opencv.hpp>
#include <stdio.h>

int main() {
    cv::Mat frame;
    cv::VideoCapture cap(0); // 打开 /dev/video0
    
    if (!cap.isOpened()) {
        printf("无法打开摄像头\n");
        return -1;
    }
    
    cap >> frame; // 采集一帧
    if (frame.empty()) {
        printf("采集失败\n");
        return -1;
    }
    
    cv::Mat gray;
    cv::cvtColor(frame, gray, cv::COLOR_BGR2GRAY);
    
    cv::imwrite("/tmp/test_gray.jpg", gray);
    printf("图像已保存到 /tmp/test_gray.jpg\n");
    
    return 0;
}

交叉编译:

arm-linux-gnueabihf-g++ test_isp.cpp -o test_isp \
    -I ./install/include/opencv4 \
    -L ./install/lib \
    -lopencv_core -lopencv_imgproc -lopencv_highgui -lopencv_videoio

把 test_isp 和 ./install/lib/ 下的so文件拷贝到板子上,运行。如果生成了 /tmp/test_gray.jpg,恭喜你,环境通了!

提示: 如果报错“找不到libopencv_xxx.so”,记得在板子上设置 export LD_LIBRARY_PATH=/your/lib/path。我刚开始总忘这步,后来写了个启动脚本自动设环境变量。

2.5 本章知识体系

下面这张图,把本章的核心逻辑串起来了:

ISP算法工程环境搭建知识体系 ISP算法工程环境搭建知识体系 交叉编译工具链 OpenCV + V4L2 嵌入式Linux系统 选择:arm-linux-gnueabihf 安装:解压 + 环境变量 验证:gcc --version 交叉编译OpenCV V4L2采集流程 NV12格式传输 内核配置:ISP驱动 根文件系统裁剪 BusyBox + 最小化 环境验证:跑通ISP流水线 输出:test_gray.jpg ✅

环境搭建是ISP算法部署的基石。工具链、OpenCV、V4L2、Linux系统,这四个东西拧在一起,才能让算法在板子上跑起来。别嫌麻烦,这一步做扎实了,后面调算法、优化性能才能顺风顺水。