# 1. 最低 CMake 版本要求建议写 3.16兼容大部分系统cmake_minimum_required(VERSION3.10)# 2. 项目定义项目名、版本、语言project(OpenCV_Demo VERSION1.0.0 LANGUAGES CXX)# 3. 设置 C 标准比如 C17set(CMAKE_CXX_STANDARD17)set(CMAKE_CXX_STANDARD_REQUIRED ON)set(CMAKE_CXX_EXTENSIONS OFF)# -----------------------------------------------------------# 3. 选项开关是否启用 CUDA# -----------------------------------------------------------# 在 cmake 配置时可以通过 -DENABLE_CUDAON 来开启option(ENABLE_CUDAEnable NVIDIA CUDA supportOFF)# 把 ENABLE_CUDA 定义传给 C 编译器if(ENABLE_CUDA)add_definitions(-DENABLE_CUDA)# 这一行必须加endif()# 输出目录设置set(EXECUTABLE_OUTPUT_PATH${PROJECT_SOURCE_DIR}/bin)# -----------------------------------------------------------# 4. 查找 OpenCV# -----------------------------------------------------------find_package(OpenCV REQUIRED)if(OpenCV_FOUND)message(STATUSOpenCV found:${OpenCV_VERSION})message(STATUSOpenCV libs:${OpenCV_LIBS})endif()# -----------------------------------------------------------# 5. 配置 ONNX Runtime# -----------------------------------------------------------if(WIN32)# --- 根据你的实际路径修改这里 ---# 如果想切换 CPU/GPU直接改这个路径或者用 CMake 变量传入if(ENABLE_CUDA)# GPU 版本路径set(ONNXRUNTIME_ROOTDIRE:/FindJob/onnxruntime-win-x64-gpu-1.14.1)message(STATUS 使用 ONNX Runtime GPU 版本)else()# CPU 版本路径set(ONNXRUNTIME_ROOTDIRE:/FindJob/onnxruntime-win-x64-1.14.1)message(STATUS 使用 ONNX Runtime CPU 版本)endif()else()# Linux 路径示例set(ONNXRUNTIME_ROOTDIR/usr/local/onnxruntime)endif()# 包含头文件目录target_include_directories(${PROJECT_NAME}PRIVATE${ONNXRUNTIME_ROOTDIR}/include)# 链接库目录target_link_directories(${PROJECT_NAME}PRIVATE${ONNXRUNTIME_ROOTDIR}/lib)# 核心库set(ONNXRUNTIME_LIBS onnxruntime)# 如果开启 CUDA需要额外链接 Provider 库if(ENABLE_CUDA)# 注意GPU 版本需要链接这两个额外的库list(APPEND ONNXRUNTIME_LIBS onnxruntime_providers_cuda)list(APPEND ONNXRUNTIME_LIBS onnxruntime_providers_shared)endif()# -----------------------------------------------------------# 6. 生成可执行文件 (关键修复必须在 target_link_libraries 之前)# -----------------------------------------------------------add_executable(${PROJECT_NAME}main.cpp)# -----------------------------------------------------------# 7. 链接库# -----------------------------------------------------------target_link_libraries(${PROJECT_NAME}PRIVATE${OpenCV_LIBS}${ONNXRUNTIME_LIBS})# -----------------------------------------------------------# 8. Windows DLL 自动拷贝 (非常重要否则运行找不到 dll)# -----------------------------------------------------------if(WIN32)# 拷贝核心 DLLadd_custom_command(TARGET${PROJECT_NAME}POST_BUILD COMMAND${CMAKE_COMMAND}-Ecopy_if_different${ONNXRUNTIME_ROOTDIR}/lib/onnxruntime.dll$TARGET_FILE_DIR:${PROJECT_NAME})# 如果开启了 CUDA还需要拷贝 Provider 的 DLLif(ENABLE_CUDA)add_custom_command(TARGET${PROJECT_NAME}POST_BUILD COMMAND${CMAKE_COMMAND}-Ecopy_if_different${ONNXRUNTIME_ROOTDIR}/lib/onnxruntime_providers_cuda.dll${ONNXRUNTIME_ROOTDIR}/lib/onnxruntime_providers_shared.dll$TARGET_FILE_DIR:${PROJECT_NAME})# 注意运行 GPU 版本还需要系统环境变量中有 CUDA 的 cudnn.dll 和 cublas.dll 等endif()endif()cmake_minimum_required(VERSION 3.10)含义声明运行此 CMake 脚本所需的最低版本。如果用户的 CMake 版本低于 3.10CMake 会报错并停止运行。作用确保脚本中使用的语法特性在当前环境中受支持。当前状态你的电脑上安装的 CMake 版本必须 ≥ 3.10。project(OpenCV_Demo VERSION 1.0.0 LANGUAGES CXX)含义定义项目名称、版本号、语言。这是 CMake 脚本中最重要的指令之一它会初始化一系列变量。关键字OpenCV_Demo这是你给项目起的名字。VERSION 1.0项目的版本号。生成的变量PROJECT_NAME值为OpenCV_Demo。PROJECT_VERSION值为1.0。PROJECT_SOURCE_DIR包含此文件的根目录。总结:给项目起名字告诉 CMake我用的语言是 C让 CMake 检查 C 编译器是否存在set(CMAKE_CXX_STANDARD 17)含义告诉 CMake 使用 C17 标准来编译代码。作用相当于在 g 编译器后面加了-stdc17参数。这让你可以使用现代 C 的特性如auto、filesystem等。set(CMAKE_CXX_STANDARD_REQUIRED ON)含义强制要求编译器支持 C17。作用如果编译器太老不支持 C17CMake 会直接报错而不是悄悄降级使用旧标准如 C98避免莫名其妙的编译错误。大白话翻译“我指定的 C 版本是必须的编译器不支持就别编译了”set(CMAKE_CXX_EXTENSIONS OFF)工程化细节。这行代码告诉编译器不要使用编译器特有的扩展比如 GCC 的 -stdgnu17而是严格遵循标准-stdc17。这能保证你的代码在不同编译器GCC, Clang, MSVC上表现一致避免“在我的电脑上能跑换个环境就挂了”的问题。CUDA 开关与宏定义option(ENABLE_CUDA Enable NVIDIA CUDA support OFF) if(ENABLE_CUDA) add_definitions(-DENABLE_CUDA) # 这一行必须加 endif()option(ENABLE_CUDA ...): 这是一个交互式开关。当你运行cmake ..时默认是 OFF。如果你想用 GPU只需运行cmake .. -DENABLE_CUDAON。这比修改代码去切换版本要专业得多。add_definitions(-DENABLE_CUDA):这是连接 CMake 和 C 代码的桥梁。作用它会在编译时给编译器传入一个-DENABLE_CUDA参数。代码里的体现在你的main.cpp里你可以这样写#ifdefENABLE_CUDA// 初始化 CUDA ProviderOrtCUDAProviderOptions cuda_options;session_options.AppendExecutionProvider_CUDA(cuda_options);#endif如果没有这一行即使你链接了 CUDA 库你的 C 代码里关于 CUDA 的初始化代码也不会被编译进去导致程序实际还是在跑 CPU。查找 OpenCVfind_package(OpenCV REQUIRED) if(OpenCV_FOUND) message(STATUS OpenCV found: ${OpenCV_VERSION}) message(STATUS OpenCV libs: ${OpenCV_LIBS}) endif()find_package(OpenCV REQUIRED): CMake 会自动去系统路径如/usr/local或环境变量OpenCV_DIR找 OpenCV 的配置文件。REQUIRED: 强制要求。如果没装 OpenCVCMake 直接报错停止防止后续出现莫名其妙的错误。message(STATUS ...): 在配置阶段打印出找到的 OpenCV 版本和库列表方便排查“为什么链接不上”的问题。此处本人是使用vcpkg 部署的opencv配置 ONNX Runtime跨平台处理# 5. 配置 ONNX Runtime if(WIN32) # --- Windows 特有逻辑 --- if(ENABLE_CUDA) set(ONNXRUNTIME_ROOTDIR E:/FindJob/onnxruntime-win-x64-gpu-1.14.1) message(STATUS 使用 ONNX Runtime GPU 版本) else() set(ONNXRUNTIME_ROOTDIR E:/FindJob/onnxruntime-win-x64-1.14.1) message(STATUS 使用 ONNX Runtime CPU 版本) endif() else() # --- Linux 特有逻辑 --- set(ONNXRUNTIME_ROOTDIR /usr/local/onnxruntime) endif() target_include_directories(${PROJECT_NAME} PRIVATE ${ONNXRUNTIME_ROOTDIR}/include) target_link_directories(${PROJECT_NAME} PRIVATE ${ONNXRUNTIME_ROOTDIR}/lib)if(WIN32): 区分操作系统。Windows 下通常使用预编译的压缩包zip路径是固定的Linux 下通常是安装到系统目录。target_include_directories: 告诉编译器去哪里找onnxruntime_cxx_api.h等头文件。target_link_directories: 告诉链接器去哪里找.lib(Windows) 或.so(Linux) 文件。注虽然现代 CMake 推荐用find_library但在部署特定版本的 ONNX Runtime 时直接指定路径硬编码或变量往往更直接有效。include_directories 和target_include_directories什么关系一句话终极结论include_directories 全局生效老写法不推荐target_include_directories 只给某个目标生效现代 CMake必须用1. 先讲清楚它们是亲戚关系但地位完全不同include_directories是老一辈的命令target_include_directories是现代 CMake的标准它们的目的完全一样告诉编译器去哪里找 .h 头文件2. 核心区别最关键include_directories(xxx)作用范围整个项目所有目标你写了这一行后面所有add_executable/add_library全部都能找到这个头文件路径缺点污染全局容易冲突target_include_directories(目标 ...)作用范围只给这一个目标只让你指定的程序/库能找到头文件,当你在代码中写 #include myheader.h 时编译器会去这个目录下查找。别的目标完全不受影响优点干净、安全、现代、无冲突3. 举个超级大白话例子假设你有项目 ├─ A.exe └─ B.exe你只想让A 能用 OpenCV 的头文件B 不能用。✅ 现代正确写法推荐add_executable(A a.cpp) # 只给 A 用 target_include_directories(A PRIVATE ${OpenCV_INCLUDE_DIRS} )❌ 老写法全局污染不推荐# 全局加路径 → A 和 B 全都能找到 include_directories(${OpenCV_INCLUDE_DIRS}) add_executable(A a.cpp) add_executable(B b.cpp) # B 也能找到这可能不是你想要的4. 最关键的三个关键字target_include_directories后面必须跟三个之一1)PRIVATE→ 路径仅给当前目标用→ 别人链接你时看不到→90% 的情况都用这个2)PUBLIC→ 自己用 别人链接你时也能用→ 给库用3)INTERFACE→ 自己不用只给别人用→ 纯头文件库用链接库CPU vs GPU# 核心库 set(ONNXRUNTIME_LIBS onnxruntime) # 如果开启 CUDA需要额外链接 Provider 库 if(ENABLE_CUDA) # 注意GPU 版本需要链接这两个额外的库 list(APPEND ONNXRUNTIME_LIBS onnxruntime_providers_cuda) list(APPEND ONNXRUNTIME_LIBS onnxruntime_providers_shared) endif()set(ONNXRUNTIME_LIBS onnxruntime): 初始化库变量包含最基础的onnxruntime.lib。list(APPEND ...):GPU 部署的坑点。ONNX Runtime 的 GPU 实现是模块化的。如果你要用 CUDA必须链接onnxruntime_providers_cuda。如果不加这两行编译可能通过但运行时会报错Failed to load shared library或者回退到 CPU 模式。生成目标与链接# 6. 生成可执行文件 add_executable(${PROJECT_NAME} main.cpp) # 7. 链接库 target_link_libraries(${PROJECT_NAME} PRIVATE ${OpenCV_LIBS} ${ONNXRUNTIME_LIBS} )add_executable: 生成.exe或可执行文件。注意这行必须在target_link_libraries之前因为链接是依附于目标的。PRIVATE: 这是一个作用域修饰符。表示这些库只用于编译当前项目不会传递给依赖当前项目的其他库虽然这里是顶层项目但加上是个好习惯。${OpenCV_LIBS}: CMake 会自动展开成opencv_world4xx.lib或者opencv_imgproc;opencv_core等一堆库。Windows DLL 自动拷贝工程化神器# 8. Windows DLL 自动拷贝 if(WIN32) # 拷贝核心 DLL add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different ${ONNXRUNTIME_ROOTDIR}/lib/onnxruntime.dll $TARGET_FILE_DIR:${PROJECT_NAME} ) # 如果开启了 CUDA还需要拷贝 Provider 的 DLL if(ENABLE_CUDA) add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different ${ONNXRUNTIME_ROOTDIR}/lib/onnxruntime_providers_cuda.dll ${ONNXRUNTIME_ROOTDIR}/lib/onnxruntime_providers_shared.dll $TARGET_FILE_DIR:${PROJECT_NAME} ) endif() endif()为什么需要这个在 Windows 上编译出来的.exe运行时需要同目录下有.dll文件。如果不拷贝你每次编译完还得手动去把 dll 复制到bin目录非常低效。add_custom_command(... POST_BUILD ...):这是一个构建后钩子。意思是当你的代码编译链接完成后CMake 会自动执行后面的命令。copy_if_different: 智能拷贝。只有当源文件比目标文件新或者目标文件不存在时才拷贝避免每次都无脑覆盖节省时间。$TARGET_FILE_DIR:...: 这是一个生成器表达式它会自动解析成你的.exe所在的目录比如bin/Release。关于set(CMAKE_EXPORT_COMPILE_COMMANDS ON)的使用这行代码是连接“构建系统”与“开发工具”的桥梁。简单来说它的作用是告诉 CMake 在编译时生成一个名为compile_commands.json的文件。这个文件是编译数据库记录了项目中每一个源文件编译时的完整命令包括编译器路径、所有的-I头文件路径、-D宏定义等。 什么时候需要它如果你只是单纯在命令行里用make或ninja编译运行这个选项不是必须的。但在以下3 种“工程化”场景中它是必选项1. 使用 VS Code / CLion 进行开发最常用当你使用 VS Code 写 C 代码时插件如 C/C 或 Clangd需要知道你的头文件在哪里定义了哪些宏。没有它编辑器只能瞎猜。它会报一堆红色的波浪线报错比如#include opencv2/... file not found即使你的项目明明能编译通过。代码跳转Go to Definition也会失效。有了它编辑器读取compile_commands.json精准还原编译环境代码补全、跳转、错误检查瞬间变准。2. 使用静态代码分析工具如果你想在项目中集成clang-tidy或cppcheck来检查代码质量。这些工具需要知道完整的编译参数比如你用了-stdc17或者定义了ENABLE_CUDA否则它们无法正确解析代码导致误报。3. 排查复杂的编译错误有时候编译报错很奇怪你不知道编译器到底用了什么参数。你可以直接打开这个 JSON 文件查看对应.cpp文件的那一行命令看看是不是漏了什么宏定义或者路径。️ 如何正确使用有两种方式开启推荐方式二。方式一在 CMakeLists.txt 中写死不推荐# 写在 project() 之前 set(CMAKE_EXPORT_COMPILE_COMMANDS ON)缺点每次配置都会生成哪怕你只是想简单编译一下。而且如果要把代码提交给不需要这个文件的人会显得多余。方式二在命令行配置时开启推荐 ✅这是最灵活的做法只在需要时生成。# 在 cmake 配置阶段加上这个参数cmake-DCMAKE_EXPORT_COMPILE_COMMANDSON..总结set(CMAKE_EXPORT_COMPILE_COMMANDS ON)是现代 C 开发特别是配合 IDE的标配。它能极大提升你的编码体验让你告别“能编译但编辑器一片红”的痛苦。对于当前项目是否要加set(CMAKE_EXPORT_COMPILE_COMMANDS ON)非常需要强烈建议加上。结合你正在做的YOLO 部署项目涉及 OpenCV、ONNX Runtime、CUDA 跨平台切换加上这行配置会让你的开发体验提升一个档次。以下是为什么你的项目特别需要它的三个理由1. 解决 VS Code 的“爆红”问题最重要你的项目中使用了大量的条件编译和自定义路径路径复杂你硬编码了E:/FindJob/onnxruntime...这样的路径。如果不生成compile_commands.jsonVS Code 的 IntelliSense 引擎很难猜到你的头文件在这些非标准目录下会导致#include onnxruntime_cxx_api.h报红即使能编译通过。宏定义复杂你用了if(ENABLE_CUDA)和add_definitions(-DENABLE_CUDA)。如果不加VS Code 不知道ENABLE_CUDA被定义了它会把#ifdef ENABLE_CUDA里面的代码比如 CUDA 初始化代码当成注释或者无效代码导致代码高亮失效、跳转失效。加了之后编辑器会精准识别“哦原来这个宏定义了那我就要检查这段 CUDA 代码的语法错误。”2. 调试 ONNX Runtime 源码做部署时你经常会遇到 ONNX Runtime 报错比如OrtStatus报错。有了这个文件配合 VS Code你可以直接跳转到 ONNX Runtime 的头文件定义处查看SessionOptions到底有哪些参数可以调而不需要去浏览器搜文档。3. 跨平台开发的“后悔药”你现在可能在 Windows 上开发以后可能要部署到 Linux。当你把代码迁移到 Linux 时加上这行配置配合 VS Code 的WSL或Remote-SSH插件它能自动帮你检测 Linux 环境下的路径对不对。如果配置错了编辑器会立刻提示而不需要你等到编译报错才发现。 怎么加推荐做法不要修改你的CMakeLists.txt文件保持它的纯净性而是在配置命令里加上它。Windows (PowerShell/CMD) 用户# 在运行 cmake 配置时加上这个参数cmake-S.-B build-DCMAKE_EXPORT_COMPILE_COMMANDSONLinux 用户cmake-DCMAKE_EXPORT_COMPILE_COMMANDSON.. 加完之后的一步操作生成后compile_commands.json会出现在build目录下。为了让 VS Code 读取到它你需要在 VS Code 的设置.vscode/c_cpp_properties.json里加一行{configurations:[{name:Win32,includePath:[${workspaceFolder}/**],compileCommands:${workspaceFolder}/build/compile_commands.json// 加上这一行指向你的生成位置}]}总结加上它你的代码提示、跳转、错误检查将瞬间变得精准这是专业 C 开发的标配。源文件配置方式选择采用add_executable(${PROJECT_NAME} main.cpp)方式还是file(GLOB SOURCES “${PROJECT_SOURCE_DIR}/src/*.cpp”)add_executable(${PROJECT_NAME} ${SOURCES})将main.cpp替换为file(GLOB ...)是管理多文件项目的常见做法但这里有一个巨大的“坑”如果不注意会让后续开发中非常抓狂。我们来详细拆解一下这两种写法并给出工业界最推荐的终极方案。⚠️ 方案一file(GLOB ...)的陷阱file(GLOB SOURCES ${PROJECT_SOURCE_DIR}/src/*.cpp) add_executable(${PROJECT_NAME} ${SOURCES})优点省事你在src目录下新建一个utils.cpp不需要改CMakeLists.txt直接编译就能自动包含进去。致命缺点坑增量更新失效CMake 的GLOB是在配置阶段运行cmake ..时执行的。场景你正在写代码突然新建了一个new_feature.cpp。现象你直接点“编译”或make编译会失败或者找不到新文件的符号。原因因为Makefile是在上一次配置时生成的它不知道今天多了一个文件。你必须重新运行cmake ..才能让它感知到新文件。这在频繁新建文件的开发初期非常打断心流。✅ 方案二现代 CMake 的最佳实践 (target_sources)工业界更推荐显式管理源文件或者使用更现代的命令。1. 显式列出最稳健虽然看起来笨但对于几百个文件的项目这是最可控的。add_executable(${PROJECT_NAME} src/main.cpp src/image_processor.cpp src/yolo_detector.cpp )优点修改文件列表一定会触发 CMake 重新配置构建系统极其稳定。缺点文件多了写起来累。2. 使用target_sourcesGLOB折中方案如果你坚持要用GLOB的便利性建议配合target_sources使用并且必须接受“新建文件后要重新配置”这个设定。# 1. 先创建目标 add_executable(${PROJECT_NAME}) # 2. 收集源文件 # 注意这里建议加上 CONFIGURE_DEPENDS (CMake 3.12) # 这会让 CMake 在构建时检查文件变动解决部分“新建文件不生效”的问题但会轻微增加配置时间 file(GLOB SOURCES CONFIGURE_DEPENDS ${PROJECT_SOURCE_DIR}/src/*.cpp) # 3. 添加源文件到目标 target_sources(${PROJECT_NAME} PRIVATE ${SOURCES}) 针对你 YOLO 项目的最终建议考虑到你的项目结构src/目录下可能有多个.cpp我建议你采用“显式列出 变量管理”的方式。这样既清晰又不会遇到 GLOB 的坑。修改后的CMakeLists.txt核心部分# ----------------------------------------------------------- # 6. 定义源文件列表 (推荐显式管理) # ----------------------------------------------------------- # 把 src 目录下的所有 cpp 文件写在这里 # 这样当你删除或重命名文件时CMake 能立刻感知并报错避免遗留垃圾代码 set(PROJECT_SOURCES src/main.cpp src/image_processor.cpp # src/yolo_detector.cpp -- 以后加新文件在这里加一行即可 ) # ----------------------------------------------------------- # 7. 生成可执行文件 # ----------------------------------------------------------- add_executable(${PROJECT_NAME} ${PROJECT_SOURCES}) # ----------------------------------------------------------- # 8. 包含头文件目录 # ----------------------------------------------------------- target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include ${OpenCV_INCLUDE_DIRS} ${ONNXRUNTIME_ROOTDIR}/include ) # ----------------------------------------------------------- # 9. 链接库 # ----------------------------------------------------------- target_link_directories(${PROJECT_NAME} PRIVATE ${ONNXRUNTIME_ROOTDIR}/lib) target_link_libraries(${PROJECT_NAME} PRIVATE ${OpenCV_LIBS} ${ONNXRUNTIME_LIBS} )总结如果处于快速原型期疯狂新建文件用file(GLOB SOURCES CONFIGURE_DEPENDS ...)但要记得新建文件后重新运行 CMake。如果处于稳定开发期文件结构基本固定用set(PROJECT_SOURCES ...)显式列出。这是最专业、最不容易出错的做法。我的建议养成显式列出文件的习惯方案2对你的长远发展更有利。在VSCode中编写CMakeLists.txt 哪些部分是会被高亮显示我直接给你最清晰、最实用、一看就懂的答案你在 VSCode 里写CMakeLists.txt哪些会高亮、哪些不会我一次性讲全✅一定会被高亮的内容核心这些是 CMake 语法的关键字/指令任何 CMake 插件都会高亮1.CMake 命令蓝色/紫色/绿色所有大写/小写命令名都会高亮project() find_package() add_executable() target_link_libraries() include_directories() set() if() else() endif() foreach()这些是核心语法→必高亮2.变量黄色/青色以${}包裹的变量${PROJECT_NAME} ${OpenCV_LIBS} ${CMAKE_CXX_STANDARD}3.字符串红色/橙色双引号包裹的文本My Project opencv44.注释绿色/灰色以#开头# 这是注释一定会高亮5.控制流关键字深蓝色if else endif while foreach function return❌不会高亮 / 高亮很弱的内容这些是你自己定义的内容插件无法识别1.你自己写的 项目名、文件名add_executable( main # 普通颜色不高亮 main.cpp # 普通颜色不高亮 )2.自定义变量名set(MY_VAR 123) # MY_VAR 不高亮3.路径、普通数字D:/opencv/include 11 20