ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

esp_dl库 yolov_detect代码解析

esp_dl库 yolov_detect代码解析 app_main,ccp源码如下#includecoco_detect.hpp#includedl_image_jpeg.hpp#includeesp_log.h#ifCONFIG_COCO_DETECT_MODEL_IN_SDCARD#includebsp/esp-bsp.h#endifexternconstuint8_tbus_jpg_start[]asm(_binary_bus_jpg_start);externconstuint8_tbus_jpg_end[]asm(_binary_bus_jpg_end);constchar*TAGyolo11n;externCvoidapp_main(void){#ifCONFIG_COCO_DETECT_MODEL_IN_SDCARDESP_ERROR_CHECK(bsp_sdcard_mount());#endif//jpeg_img_t是dl::image命名空间下的一个结构体类型表示JPEG图像数据//在 dl 这个大命名空间里找到 image 子命名空间再从中取出 jpeg_img_t 这个类型用它来定义变量 jpeg_imgdl::image::jpeg_img_tjpeg_img{.data(void*)bus_jpg_start,.data_len(size_t)(bus_jpg_end-bus_jpg_start)};autoimgdl::image::sw_decode_jpeg(jpeg_img,dl::image::DL_IMAGE_PIX_TYPE_RGB888);COCODetect*detectnewCOCODetect();autodetect_resultsdetect-run(img);for(constautores:detect_results){ESP_LOGI(TAG,[category: %d, score: %f, x1: %d, y1: %d, x2: %d, y2: %d],res.category,res.score,res.box[0],res.box[1],res.box[2],res.box[3]);}delete detect;heap_caps_free(img.data);#ifCONFIG_COCO_DETECT_MODEL_IN_SDCARDESP_ERROR_CHECK(bsp_sdcard_unmount());#endif}逐行代码解读1.asm(“_binary_bus_jpg_start”);extern const uint8_t bus_jpg_start[] asm(“_binary_bus_jpg_start”);extern const uint8_t bus_jpg_end[] asm(“_binary_bus_jpg_end”);asm(“_binary_bus_jpg_start”) 的含义这是 GCC 编译器扩展语法它的作用相当于给 C 变量起了一个汇编层面的别名。你可以把它理解为C 代码里我叫 bus_jpg_start但链接器在二进制文件里找我的名字时请认准 _binary_bus_jpg_start部分含义extern这个变量不在当前文件定义是在别处链接器生成的目标文件里定义的const uint8_t类型无符号 8 位整数常量即字节数组bus_jpg_start[]C 层面的变量名数组形式长度未知由链接器决定asm(_binary_bus_jpg_start)GCC 扩展告诉编译器这个变量在汇编层面实际对应的符号名是_binary_bus_jpg_startextern说明定义不在当前文件中定义但是我们找遍代码发现它没有定义的地方其实他在CMakeLists.txt文件中定义定义如下旧版 ESP-IDF方式set(embed_filesbus.jpg)# 或者多个文件set(embed_filesbus.jpglogo.pngmodel.bin)新版定义方式target_add_aligned_binary_data(${COMPONENT_LIB}images/bus.jpgBINARY)2.代码讲解dl::image::jpeg_img_t jpeg_img {.data (void *)bus_jpg_start, .data_len (size_t)(bus_jpg_end - bus_jpg_start)};在 dl 这个大命名空间里找到 image 子命名空间再从中取出 jpeg_img_t 这个类型用它来定义变量 jpeg_img。符号含义dlESP-DL 库的根命名空间namespace所有 ESP-DL 的 API 都在这里面imagedl下的子命名空间专门封装图像编解码、预处理相关功能jpeg_img_tdl::image内部定义的一个结构体或 typedef用来描述一张 JPEG 图片的元数据bus_jpg_start / bus_jpg_end 是什么这两个名字通常是链接器自动生成的符号。在 ESP-IDF 中如果你用 COMPONENT_EMBED_FILES 或 target_add_binary_data 把一张 .jpg 图片嵌入到固件里链接器会自动生成两个符号bus_jpg_start图片数据在 Flash 中的起始地址bus_jpg_end图片数据在 Flash 中的结束地址所以这段代码的整体逻辑就是把嵌入到固件中的 bus.jpg 图片包装成 ESP-DL 能识别的 dl::image::jpeg_img_t 结构体后续就可以传给 ESP-DL 的图像解码函数去处理。3. auto img dl::image::sw_decode_jpeg(jpeg_img, dl::image::DL_IMAGE_PIX_TYPE_RGB888);auto 是 C11 引入的关键字让编译器自动推断变量类型不用手动写出那个可能很长的类型名。在 ESP-DL 中sw_decode_jpeg 的返回值通常是一个封装好的图像张量/矩阵对象dl::image::sw_decode_jpeg —— 函数全名层级含义dlESP-DL 库的根命名空间imagedl下的图像处理子命名空间sw_decode_jpeg具体的函数名软件Software解码 JPEG参数讲解参数dl::image::DL_IMAGE_PIX_TYPE_RGB888部分含义dl::image同样的命名空间DL_IMAGE_PIX_TYPE_RGB888像素格式每个像素用3 字节表示分别是R红、G绿、B蓝每个通道 8 位共 24 位ESP-DL 的神经网络模型通常要求输入是 RGB888 格式所以这里指定把 JPEG 解码成 RGB 裸数据方便后续直接喂给模型。4.COCODetect *detect new COCODetect();这行代码是 C 中在堆内存上动态创建一个目标检测模型对象。逐段拆解如下COCODetect —— 模型类这是 ESP-DL Model Zoo 里提供的一个目标检测模型类名字里的 COCO 指的是 MS COCO 数据集一个包含 80 类常见物体的标准数据集如人、车、猫、狗、椅子等。new COCODetect() —— 动态内存分配这是 C 的 new 运算符做两件事步骤动作① 分配内存在堆Heap上申请一块足够大的连续内存② 调用构造函数在这块内存上执行COCODetect::COCODetect()初始化模型权重、配置参数等为什么用 new放在栈上 而不是直接定义对象方式代码内存位置特点栈分配COCODetect detect;栈Stack自动释放但 ESP32 栈通常只有8~16KB模型权重根本放不下堆分配✅new COCODetect()堆Heap/PSRAM空间大几百 KB 到几 MB生命周期由程序员控制但必须手动释放模型权重本身可能就占 几百 KB加上中间特征图缓存只能放堆上。5.auto detect_results detect-run(img);这行代码是 通过指针调用目标检测模型的推理方法并用引用接收结果。承接你之前创建的对象逐段拆解detect-run(img) —— 指针调用成员函数| 符号 | 含义 || ---------- | ------------------------------------------------- ||detect| 之前new COCODetect()创建的指针COCODetect*类型 ||-|箭头运算符用于通过指针访问对象的成员函数或变量 ||.run()| 等价于(*detect).run()即先解引用指针得到对象再调用方法 ||run(img)| 让模型对图像img执行一次前向推理Forward Inference|run(img) 内部在做什么这是模型推理的核心一步输入: img (RGB888 图像张量如 320×320×3)│▼┌──────────────────┐│ 预处理 │ 归一化、减均值、除标准差、调整尺寸│ (Preprocess) │└──────────────────┘│▼┌──────────────────┐│ 神经网络前向传播 │ 卷积 → 激活 → 池化 → … → 输出层│ (Forward) │└──────────────────┘│▼┌──────────────────┐│ 后处理 │ NMS 非极大值抑制、置信度过滤、解码边界框│ (Postprocess) │└──────────────────┘│▼输出: 检测框列表每个框含类别、分数、坐标4.auto detect_results —— 引用接收返回值部分含义auto编译器自动推导返回类型通常是std::vectorDetectResult或dl::Tensor等引用Reference给返回值起别名不拷贝数据为什么加 很重要run() 返回的检测结果通常是一个包含多个检测框的列表/向量数据量不小。如果写成auto detect_results detect-run(img); // ❌ 拷贝把结果复制一份到新变量auto detect_results detect-run(img); // ✅ 引用直接绑定到原结果零拷贝6.坐标显示与内存释放for(constautores:detect_results){ESP_LOGI(TAG,[category: %d, score: %f, x1: %d, y1: %d, x2: %d, y2: %d],res.category,res.score,res.box[0],res.box[1],res.box[2],res.box[3]);}deletedetect;heap_caps_free(img.data);细节讲解。函数内部的操作。COCODetect *detect new COCODetect();内部详解源代码如下COCODetect::COCODetect(model_type_t model_type,boollazy_load):m_model_type(model_type){switch(model_type){casemodel_type_t::YOLO11N_S8_V1:casemodel_type_t::YOLO11N_320_S8_V1:m_score_thr[0]coco_detect::Yolo11n::default_score_thr;m_nms_thr[0]coco_detect::Yolo11n::default_nms_thr;break;}if(lazy_load){m_modelnullptr;}else{load_model();}}因为switch语句第一条没有break所以m_score_thr[0] coco_detect::Yolo11n::default_score_thr;m_nms_thr[0] coco_detect::Yolo11n::default_nms_thr;最后两行代码都会执行。coco_detect::Yolo11n的定义如下其中default_score_thr表示置信度阈值default_nms_thr非极大值抑制阈值 / 交并比阈值namespacecoco_detect{classYolo11n:publicdl::detect::DetectImpl{public:staticconstexprfloatdefault_score_thr0.25;staticconstexprfloatdefault_nms_thr0.7;Yolo11n(constchar*model_name,floatscore_thr,floatnms_thr);};}// namespace coco_detectSome of you may be curious about how these parameters are passed.参数在coco_detect.hpp类COCODetect的构造中COCODetect(model_type_t model_type static_castmodel_type_t(CONFIG_DEFAULT_COCO_DETECT_MODEL),bool lazy_load true);源码如下。classCOCODetect:publicdl::detect::DetectWrapper{public:typedefenum{YOLO11N_S8_V1,YOLO11N_320_S8_V1,}model_type_t;COCODetect(model_type_t model_typestatic_castmodel_type_t(CONFIG_DEFAULT_COCO_DETECT_MODEL),boollazy_loadtrue);private:voidload_model()override;model_type_t m_model_type;};紧接着讲COCODetect *detect new COCODetect();因为lazy_loadtrue所以执行m_model nullptr;也就是不加载模型然后我回到了主函数执行 auto detect_results detect-run(img);进入detect-run(img);源代码如下。std::vectordl::cls::result_tClsWrapper::run(constdl::image::img_timg,runtime_mode_t mode){if(!m_model){load_model();}returnm_model-run(img,mode);}其中 load_model();用来加载模型进入到 load_model();源码如下voidCOCODetect::load_model(){switch(m_model_type){casemodel_type_t::YOLO11N_S8_V1:#ifCONFIG_FLASH_COCO_DETECT_YOLO11N_S8_V1||CONFIG_COCO_DETECT_MODEL_IN_SDCARDm_modelnewcoco_detect::Yolo11n(coco_detect_yolo11n_s8_v1.espdl,m_score_thr[0],m_nms_thr[0]);#elseESP_LOGE(coco_detect,coco_detect_yolo11n_s8_v1 is not selected in menuconfig.);#endifbreak;casemodel_type_t::YOLO11N_320_S8_V1:#ifCONFIG_FLASH_COCO_DETECT_YOLO11N_320_S8_V1||CONFIG_COCO_DETECT_MODEL_IN_SDCARDm_modelnewcoco_detect::Yolo11n(coco_detect_yolo11n_320_s8_v1.espdl,m_score_thr[0],m_nms_thr[0]);#elseESP_LOGE(coco_detect,coco_detect_yolo11n_320_s8_v1 is not selected in menuconfig.);#endifbreak;}}模型加载核心代码m_model new coco_detect::Yolo11n(“coco_detect_yolo11n_s8_v1.espdl”, m_score_thr[0], m_nms_thr[0]);模型位置定义放在models\coco_detect\CMakeLists.txt中定义如下if(CONFIG_FLASH_COCO_DETECT_YOLO11N_S8_V1)list(APPEND models ${models_dir}/coco_detect_yolo11n_s8_v1.espdl)m_model new coco_detect::Yolo11n(“coco_detect_yolo11n_320_s8_v1.espdl”, m_score_thr[0], m_nms_thr[0]);源码如下namespacecoco_detect{Yolo11n::Yolo11n(constchar*model_name,floatscore_thr,floatnms_thr){#if!CONFIG_COCO_DETECT_MODEL_IN_SDCARDboolparam_copytrue;if(heap_caps_get_total_size(MALLOC_CAP_SPIRAM)1024*1024*9){//内存申请param_copyfalse;}m_modelnewdl::Model(path,model_name,static_castfbs::model_location_type_t(CONFIG_COCO_DETECT_MODEL_LOCATION),0,dl::MEMORY_MANAGER_GREEDY,nullptr,param_copy);#elseautosd_pathstd::filesystem::path(CONFIG_BSP_SD_MOUNT_POINT)/CONFIG_COCO_DETECT_MODEL_SDCARD_DIR/model_name;m_modelnewdl::Model(sd_path.c_str(),fbs::MODEL_LOCATION_IN_SDCARD);#endifm_model-minimize();m_image_preprocessornewdl::image::ImagePreprocessor(m_model,{0,0,0},{255,255,255});m_image_preprocessor-enable_letterbox({114,114,114});m_postprocessornewdl::detect::yolo11PostProcessor(m_model,m_image_preprocessor,0.25,0.7,10,{{8,8,4,4},{16,16,8,8},{32,32,16,16}});}}// namespace coco_detect代码行功能说明作用/目的m_model-minimize();模型内存瘦身释放.espdl解析阶段产生的临时缓冲区、元数据表等仅保留推理必需的权重张量和网络结构降低内存占用m_image_preprocessor new dl::image::ImagePreprocessor(m_model, {0, 0, 0}, {255, 255, 255});创建图像预处理器实例化预处理对象绑定当前模型获取输入尺寸要求设置归一化参数mean0, std255将像素值从[0,255]线性缩放到[0.0, 1.0]m_image_preprocessor-enable_letterbox({114, 114, 114});启用 Letterbox 缩放开启保持宽高比的等比缩放模式缩放后不足模型输入尺寸的部分用 RGB(114,114,114) 中灰色填充防止图像拉伸变形导致检测精度下降m_postprocessor new dl::detect::yolo11PostProcessor(m_model, m_image_preprocessor, 0.25, 0.7, 10, {{8, 8, 4, 4}, {16, 16, 8, 8}, {32, 32, 16, 16}});创建 YOLO11 后处理器实例化结果解码器绑定模型和预处理器设置置信度阈值0.25低于 25% 的框丢弃、NMS IoU 阈值0.7重叠度超过 70% 的去重、每类最多保留10个框传入三级特征图的 stride/anchor 配置用于将神经网络输出的原始张量解码为带类别、分数、坐标的检测框列表模型加载完毕后回到std::vectordl::cls::result_t ClsWrapper::run(const dl::image::img_t img, runtime_mode_t mode)最后执行m_model-run(img, mode);返回坐标信息
返回列表