YOLO 的完整 C 语言实现,尤其是像 YOLOv5、YOLOv8 这样的现代版本,通常不是一个从头开始的“Hello World”级别的项目。 它是一个高度优化的、复杂的计算机视觉系统,包含了大量的数学运算(卷积、池化)、网络结构定义、数据处理和后处理逻辑。

(图片来源网络,侵删)
实现 YOLO 的 C 语言代码主要有以下几种形式:
- 基于 Darknet 框架 (最经典、最完整的 C 实现)
- 使用 ONNX Runtime 或 TensorRT 的 C API (生产环境推荐)
- 从零开始编写一个极简版 YOLOv1 (用于学习原理)
下面我将分别对这三种方式进行介绍,并提供核心代码示例和资源链接。
基于 Darknet 框架 (最经典)
Darknet 是 YOLO 的原始作者 Joseph Redmon 开发的轻量级、开源的神经网络框架,它完全用 C 语言和 CUDA 编写,是学习和使用 YOLO 最经典的 C 语言环境。
核心特点
- 纯 C/CUDA: 没有复杂的 C++ 依赖,非常适合在 NVIDIA GPU 上进行高性能推理。
- 命令行工具: 通过简单的命令行即可完成训练和检测。
- 模块化设计: 代码结构清晰,包含网络层定义 (
layer.h)、数据加载 (data.c)、推理 (network.c) 等模块。
核心代码结构解析
当你克隆并打开 Darknet 的源码仓库时,你会看到以下关键文件:

(图片来源网络,侵删)
src/network.c: 网络的核心,包含了前向传播 (forward_network), 反向传播 (backward_network), 网络初始化等所有与网络计算相关的函数。src/layer.h&src/layer.c: 网络层的定义,定义了各种层结构,如卷积层 (CONVOLUTIONAL)、池化层 (MAXPOOL)、激活层 (RELU)、连接层 (ROUTE) 等,每个层都有其特定的forward和backward函数。src/utils.c: 工具函数,包含数学工具、计时、日志等辅助功能。src/detector.c: 检测器入口,包含了predict_network函数,这是进行目标检测调用的主要函数,它负责加载网络、预处理图像、执行推理和后处理(NMS)。src/image.c: 图像处理,包含图像的加载、缩放、填充、颜色空间转换等操作。src/parser.c: 配置文件解析,用于解析.cfg网络结构文件。
核心推理代码示例 (detector.c)
detector.c 中的 detect_image 函数是推理的缩影,下面是一个简化的逻辑流程:
// 在 detector.c 中
image detect_image(network *net, image im)
{
// 1. 预处理图像
// 调整图像大小以适应网络输入,并进行归一化等操作
image sized = letterbox_image(im, net->w, net->h); // letterbox 会保持长宽比,填充背景
network_predict(net, sized.data); // 这是核心的推理调用!
// 2. 后处理
// network_predict 之后,网络的输出就在 net->output 中
// net->output 是一个 float 数组,包含了所有预测框的置信度和类别概率
// 这里需要手动实现或调用 Darknet 提供的后处理函数
// 通常包括:
// a) 过滤掉低置信度的框
// b) 将预测框的坐标从网络输入尺寸转换回原始图像尺寸
// c) 执行非极大值抑制 来消除重叠的框
// ... (后处理逻辑非常复杂,通常调用如 do_nms_obj 等)
// 返回包含所有检测框信息的图像结构
// return dets_image;
}
network_predict 函数是关键,它遍历网络的所有层,逐层执行前向传播,最终得到输出。
如何开始
- 克隆 Darknet:
git clone https://github.com/pjreddie/darknet.git cd darknet
- 编译:
- 仅 CPU:
make
- 启用 CUDA (GPU):
sed -i 's/OPENCV=0/OPENCV=1/' Makefile sed -i 's/GPU=0/GPU=1/' Makefile sed -i 's/CUDNN=0/CUDNN=1/' Makefile make
- 仅 CPU:
- 下载预训练模型:
wget https://pjreddie.com/media/files/yolov3.weights
- 运行检测:
./darknet detect cfg/yolov3.cfg yolov3.weights data/dog.jpg
资源链接:
使用 ONNX Runtime 或 TensorRT (生产环境推荐)
在现代应用中,很少有人直接使用 Darknet,通常的做法是:

(图片来源网络,侵删)
- 用 Python (PyTorch/TensorFlow) 训练好 YOLO 模型。
- 将模型导出为标准格式,如 ONNX (Open Neural Network Exchange)。
- 使用 C/C++ 调用 ONNX Runtime 或 NVIDIA TensorRT 的 C API 来加载模型并进行高性能推理。
这种方法结合了 Python 生态的便利性和 C 语言的性能。
核心思路
- 模型转换:
PyTorch Model -> ONNX Model - C++ 加载: 在 C++ 项目中集成 ONNX Runtime 库。
- 推理流程:
- 初始化 ONNX Runtime 会话。
- 读取输入图像,进行预处理(缩放、归一化、转换为
float32格式)。 - 将处理后的图像数据作为输入,调用会话的
Run方法。 - 获取模型的输出(一堆浮点数)。
- 手动实现后处理:这部分和 Darknet 类似,是 C 语言实现 YOLO 的核心难点。
- 解析后处理结果,绘制边界框。
核心代码示例 (使用 ONNX Runtime C API)
这是一个概念性的代码片段,展示了如何在 C++ 中使用 ONNX Runtime。
#include <onnxruntime_c_api.h>
#include <opencv2/opencv.hpp>
// 假设模型输入是 (1, 3, 640, 640)
const int input_width = 640;
const int input_height = 640;
const int input_channels = 3;
void run_yolov8_onnx(const char* model_path, const char* image_path) {
// 1. 初始化 ONNX Runtime
OrtEnv* env;
OrtCreateEnv(ORT_LOGGING_LEVEL_WARNING, "YOLO", &env);
OrtSession* session;
OrtSessionOptions* session_options;
OrtSessionOptionsCreate(&session_options);
OrtSessionOptionsSetIntraOpNumThreads(session_options, 1);
OrtSessionOptionsSetExecutionMode(session_options, ORT_SEQUENTIAL);
OrtCreateSession(env, model_path, session_options, &session);
// 2. 准备输入输出
// ... (获取输入输出名称、形状和类型) ...
OrtAllocator* allocator;
OrtCreateAllocator(env, &allocator);
// 3. 加载并预处理图像
cv::Mat img = cv::imread(image_path);
cv::Mat resized_img, blob;
cv::resize(img, resized_img, cv::Size(input_width, input_height));
blob = cv::dnn::blobFromImage(resized_img, 1.0/255.0, cv::Size(input_width, input_height), cv::Scalar(0,0,0), true, false, CV_32F);
// 4. 创建输入 Tensor
OrtMemoryInfo* memory_info;
OrtCreateCpuMemoryInfo(ORT_CPU_DEVICE_ID, OrtMemTypeDefault, &memory_info);
OrtValue* input_tensor = nullptr;
OrtCreateTensorWithDataAsOrtValue(memory_info, blob.ptr<float>(), blob.total() * blob.elemSize(), input_node_dims, ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT, &input_tensor);
// 5. 运行推理
OrtValue* output_tensor = nullptr;
session->Run(session, &input_name, &input_tensor, 1, output_names, 1, &output_tensor);
// 6. 获取输出数据并执行后处理
float* float_output = nullptr;
OrtGetTensorMutableData(output_tensor, (void**)&float_output);
// !!! 核心部分:后处理 !!!
// 你需要自己编写 C++ 代码来解析 float_output
// 这包括:
// - 将输出张量重塑为 [num_boxes, 4 + num_classes] 的格式
// - 应用置信度阈值过滤
// - 将边界框坐标从归一化坐标转换回原图坐标
// - 执行 NMS
std::vector<Detection> detections = post_process(float_output, img.size());
// 7. 释放资源
// ... (释放所有 Ort 指针) ...
}
资源链接:
从零开始编写一个极简版 YOLOv1 (用于学习)
如果你想真正理解 YOLO 的工作原理,而不只是使用一个框架,可以尝试用 C 语言实现一个最原始的 YOLOv1,这个版本没有复杂的锚框和特征金字塔,非常适合学习。
极简 YOLOv1 的逻辑
- 输入: 一个
S x S x (B*5 + C)的张量。S x S: 将图像划分为S x S的网格。B: 每个网格预测的边界框数量 (YOLOv1 中 B=2)。5: 每个框的 5 个预测值x, y, w, h, confidence。C: 类别数量。
- 输出: 经过后处理的边界框列表。
- 损失函数: 包含定位损失、置信度损失和分类损失。
- 后处理:
- 过滤掉
confidence * class_prob低于阈值的框。 - 对每个类别执行 非极大值抑制。
- 过滤掉
核心后处理 C 语言代码示例
这是实现 YOLO 最关键、也最考验编程能力的部分,下面是一个简化的 NMS 函数框架。
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
// 定义一个边界框结构体
typedef struct {
float x, y, w, h; // 中心坐标和宽高
float confidence;
int class_id;
} Box;
// 定义一个检测结果,包含框和类别信息
typedef struct {
Box box;
int class_id;
float prob;
} Detection;
// 两个框的交并比
float iou(Box a, Box b) {
// 计算交集区域
float inter_x1 = fmax(a.x - a.w/2, b.x - b.w/2);
float inter_y1 = fmax(a.y - a.h/2, b.y - b.h/2);
float inter_x2 = fmin(a.x + a.w/2, b.x + b.w/2);
float inter_y2 = fmin(a.y + a.h/2, b.y + b.h/2);
if (inter_x2 < inter_x1 || inter_y2 < inter_y1) return 0.0f;
float inter_area = (inter_x2 - inter_x1) * (inter_y2 - inter_y1);
float a_area = a.w * a.h;
float b_area = b.w * b.h;
return inter_area / (a_area + b_area - inter_area);
}
// 非极大值抑制
void nms(Detection* dets, int total, float threshold, Detection* keep, int* num_out) {
*num_out = 0;
// 1. 按 confidence 降序排序
// ... (实现一个排序算法,如冒泡排序或快速排序) ...
// 为了示例,我们假设已经排序好了
// 2. 遍历排序后的检测框
for (int i = 0; i < total; i++) {
if (dets[i].prob == 0) continue; // 已被抑制的框
keep[(*num_out)++] = dets[i];
// 3. 抑制与当前框 IoU 大于阈值的同类框
for (int j = i + 1; j < total; j++) {
if (dets[j].class_id == dets[i].class_id) {
if (iou(dets[i].box, dets[j].box) > threshold) {
dets[j].prob = 0; // 标记为抑制
}
}
}
}
}
// 主函数调用示例
int main() {
// 假设这是网络未经处理的原始输出,经过解析后得到的一系列检测框
Detection detections[] = {
{{10, 10, 20, 20}, 0.9, 0, 0.9}, // class 0
{{12, 12, 22, 22}, 0.8, 0, 0.8}, // class 0
{{100, 100, 30, 30}, 0.95, 1, 0.95}, // class 1
{ {101, 101, 31, 31}, 0.85, 1, 0.85} // class 1
};
int total_dets = 4;
Detection keep[10];
int num_keep;
nms(detections, total_dets, 0.5f, keep, &num_keep);
printf("NMS Result: %d detections kept.\n", num_keep);
for (int i = 0; i < num_keep; i++) {
printf("Det %d: class=%d, conf=%.2f\n", i, keep[i].class_id, keep[i].prob);
}
return 0;
}
总结与建议
| 方法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| Darknet | 经典、完整、纯C/CUDA、社区成熟 | API较旧、难以集成到现代C++项目、依赖OpenCV/CUDA | 学习YOLO原理、在Linux/GPU环境下进行快速验证 |
| ONNX/TensorRT | 性能极高、模型格式标准、易于部署、与现代C++项目集成 | 后处理需自己实现、依赖第三方库(如OpenCV) | 生产环境的嵌入式、服务器端应用 |
| 从零实现 | 深入理解YOLO细节、无外部依赖 | 工作量巨大、性能差、难以处理复杂模型 | 学术研究、计算机视觉教学、面试准备 |
给你的建议:
- 如果你是初学者,想快速运行YOLO: 使用 Darknet,按照它的教程走一遍。
- 如果你想做一个实际的产品或项目: 使用 PyTorch/TensorFlow 训练 + ONNX/TensorRT 推理 的模式,这是业界标准。
- 如果你想深入理解YOLO的每一个细节: 尝试 从零实现一个YOLOv1,重点攻克后处理部分的代码,这个过程会让你对目标检测有非常深刻的认识。
