3. Python 与 C++ 混合编译环境
说实话,工业缺陷检测的模型移植,最折腾人的往往不是模型本身,而是环境。Python 负责快速验证和训练,C++ 负责在边缘设备上跑推理。这两套语言怎么在同一个开发机上和平共处?这就是本章要解决的问题。
我个人习惯把环境搭建分成四块:Python 虚拟环境、Python 本体、交叉编译工具链、CMake 构建系统。咱们一块一块来。
核心思路:Python 管算法验证,C++ 管嵌入式部署。两者通过 pybind11 或 ONNX Runtime 的 C++ API 桥接。环境隔离用 Anaconda,编译统一用 CMake。
3.1 Anaconda 虚拟环境管理
为什么非要用虚拟环境?我踩过这个坑。早期做项目时,直接在系统 Python 里装包,结果 TensorFlow 要 2.4,PyTorch 要 1.7,两个库的依赖打架,最后重装了系统。从那以后,我每个项目都建独立的虚拟环境。
Anaconda 的好处是:它不光管 Python 包,还能管非 Python 的二进制依赖,比如 CUDA 驱动、OpenCV 的底层库。这一点 pip 做不到。
安装 Anaconda
去官网下载 Linux 版安装脚本。我个人建议装 Miniconda 而不是完整版 Anaconda——完整版预装了一堆你用不到的包,占空间还容易冲突。
# 下载 Miniconda 安装脚本
wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh
# 运行安装
bash Miniconda3-latest-Linux-x86_64.sh
# 安装完成后,激活 base 环境
source ~/.bashrc
安装过程中会问你是否要初始化 conda,选 yes。这样每次打开终端就能直接用 conda 命令。
创建和管理虚拟环境
我习惯按项目名+Python版本来命名环境,比如 defect_detection_py38。这样一眼就知道这个环境是干什么的。
# 创建环境,指定 Python 3.8
conda create -n defect_detection_py38 python=3.8
# 激活环境
conda activate defect_detection_py38
# 查看已有环境
conda env list
# 退出环境
conda deactivate
# 删除环境(如果搞坏了)
conda env remove -n defect_detection_py38
小技巧:用 conda create -n myenv python=3.8 --clone base 可以快速复制一个已有环境。我在迁移项目时经常用这个命令,省得重新装一堆包。
环境导出与复现
团队协作时,环境一致性是个大问题。我吃过这个亏:同事的环境跑得好好的,我这边就是报错。后来强制要求每个人用 environment.yml 来复现环境。
# 导出当前环境配置
conda env export > environment.yml
# 从 yml 文件创建环境
conda env create -f environment.yml
| 命令 | 作用 | 常用场景 |
|---|---|---|
conda create |
创建新环境 | 项目初始化 |
conda activate |
激活环境 | 切换项目 |
conda env export |
导出环境配置 | 团队共享、CI/CD |
conda env remove |
删除环境 | 清理废弃项目 |
3.2 Python 3.8+ 安装与配置
为什么选 Python 3.8?不是因为它最新,而是因为工业界的主流推理框架(ONNX Runtime、OpenVINO、TensorRT)对 3.8 的支持最稳定。3.9 和 3.10 虽然新,但有些嵌入式平台的预编译包还没跟上。
用 Anaconda 创建环境时,Python 版本已经指定好了。但有时候你需要手动装一个独立的 Python,比如在 Docker 镜像里。
# 查看当前 Python 版本
python --version
# 安装额外的 Python 包(在 conda 环境内)
pip install numpy opencv-python pillow
# 查看已安装包列表
pip list
注意:在 conda 环境里,优先用 conda install 装包。如果 conda 源里没有,再用 pip。混用 conda 和 pip 可能导致依赖冲突。我曾经在一个环境里混用了两种方式装 PyTorch,结果运行时 segfault,排查了两天才发现是 libcudart 版本不一致。
配置 pip 国内镜像
国内下载 PyPI 包经常超时。我一般配清华或阿里云的镜像,速度能快 10 倍。
# 临时使用
pip install numpy -i https://pypi.tuna.tsinghua.edu.cn/simple
# 永久配置
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
3.3 GCC/G++ 交叉编译工具链安装
交叉编译,说白了就是在你的 x86 电脑上编译出能在 ARM 开发板上运行的代码。你想想看,开发板那点算力,编译一个大型 C++ 项目可能要几个小时,而在 PC 上只要几分钟。
工业缺陷检测的常见目标平台有:ARM Cortex-A 系列(树莓派、瑞芯微)、NVIDIA Jetson 系列(TX2、Xavier、Orin)。不同平台需要不同的交叉编译工具链。
安装交叉编译工具链
以 ARM64(aarch64)平台为例:
# Ubuntu 上安装 ARM64 交叉编译工具链
sudo apt-get update
sudo apt-get install gcc-aarch64-linux-gnu g++-aarch64-linux-gnu
# 验证安装
aarch64-linux-gnu-gcc --version
aarch64-linux-gnu-g++ --version
对于 NVIDIA Jetson 平台,它本身就是 ARM64 架构,但 JetPack SDK 里包含了专用的交叉编译工具链。我建议直接用 NVIDIA 官方提供的 l4t-gcc。
| 目标平台 | 架构 | 工具链前缀 | 安装方式 |
|---|---|---|---|
| 树莓派 4B | armv7l / aarch64 | arm-linux-gnueabihf- | apt-get install |
| 瑞芯微 RK3588 | aarch64 | aarch64-linux-gnu- | apt-get install |
| Jetson Orin | aarch64 | aarch64-linux-gnu- | JetPack SDK |
| 全志 V系列 | armv7l | arm-linux-gnueabihf- | 厂商提供 |
测试交叉编译
写一个简单的 Hello World 来验证工具链是否正常工作:
// hello.cpp
#include <iostream>
int main() {
std::cout << "Hello from ARM64!" << std::endl;
return 0;
}
# 交叉编译
aarch64-linux-gnu-g++ hello.cpp -o hello_arm64
# 查看生成的文件类型
file hello_arm64
# 输出:ELF 64-bit LSB executable, ARM aarch64, ...
看到 ARM aarch64 就说明交叉编译成功了。把这个文件传到开发板上就能直接运行。
避坑指南:我曾经编译了一个程序,在开发板上运行时报错 /lib/ld-linux-aarch64.so.1: No such file or directory。原因是开发板上的动态链接器版本和工具链不匹配。解决办法是:用 aarch64-linux-gnu-g++ -static 静态编译,或者确保工具链版本与开发板的系统版本一致。
3.4 CMake 构建系统入门
CMake 是 C++ 项目的构建工具。你想想看,一个工业缺陷检测项目,可能有几十个源文件,依赖 OpenCV、ONNX Runtime、TensorRT 等库。手动写 Makefile 会疯掉的。CMake 帮你自动处理依赖、编译选项、跨平台适配。
安装 CMake
# Ubuntu 安装
sudo apt-get install cmake
# 验证版本(建议 3.16 以上)
cmake --version
如果系统自带的版本太低,可以去 CMake 官网 下载预编译的二进制包,解压后直接放到 /usr/local 下。
一个简单的 CMakeLists.txt
这是我最常用的模板,适用于大多数嵌入式推理项目:
cmake_minimum_required(VERSION 3.16)
project(DefectDetectionInference)
# 设置 C++ 标准
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# 设置交叉编译(如果启用)
if(CMAKE_CROSSCOMPILING)
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR aarch64)
set(CMAKE_C_COMPILER aarch64-linux-gnu-gcc)
set(CMAKE_CXX_COMPILER aarch64-linux-gnu-g++)
endif()
# 查找依赖包
find_package(OpenCV REQUIRED)
find_package(ONNXRuntime REQUIRED)
# 添加可执行文件
add_executable(inference_app
src/main.cpp
src/detector.cpp
src/preprocess.cpp
)
# 链接库
target_link_libraries(inference_app
${OpenCV_LIBS}
${ONNXRuntime_LIBRARIES}
)
# 设置输出目录
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
编译流程
CMake 的编译流程分两步:配置和构建。我习惯在项目根目录下建一个 build 文件夹,所有编译产物都放里面,保持源码目录干净。
# 1. 创建 build 目录
mkdir build && cd build
# 2. 配置(生成 Makefile)
cmake ..
# 3. 构建(编译)
make -j4
# 4. 查看编译产物
ls bin/
如果是交叉编译,需要在配置时指定工具链文件:
cmake .. -DCMAKE_TOOLCHAIN_FILE=../toolchain.cmake
toolchain.cmake 文件内容示例:
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR aarch64)
set(CMAKE_C_COMPILER aarch64-linux-gnu-gcc)
set(CMAKE_CXX_COMPILER aarch64-linux-gnu-g++)
set(CMAKE_FIND_ROOT_PATH /usr/aarch64-linux-gnu)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
注意:交叉编译时,find_package 会去系统路径找库。如果目标平台的库不在默认路径,需要用 CMAKE_FIND_ROOT_PATH 指定搜索路径。我遇到过 OpenCV 找到了 x86 版本而不是 ARM 版本,导致链接失败。解决办法是把 ARM 版的 OpenCV 安装到 /usr/aarch64-linux-gnu 下,然后在 toolchain 文件里指定这个路径。
常用 CMake 变量速查
| 变量名 | 作用 | 示例值 |
|---|---|---|
CMAKE_CXX_STANDARD |
C++ 标准版本 | 17 |
CMAKE_BUILD_TYPE |
编译模式 | Release / Debug |
CMAKE_INSTALL_PREFIX |
安装路径 | /usr/local |
CMAKE_TOOLCHAIN_FILE |
交叉编译工具链文件 | toolchain.cmake |
CMAKE_RUNTIME_OUTPUT_DIRECTORY |
可执行文件输出目录 | ${CMAKE_BINARY_DIR}/bin |
嗯,到这里,Python 与 C++ 混合编译环境的核心内容就讲完了。Anaconda 管 Python 环境,交叉编译工具链管 C++ 编译,CMake 把整个构建流程串起来。这三样东西配合好了,后面的模型移植工作才能顺利推进。