视觉与机电作品 / 固定输入推理实验发布 内容更新

ESP-DL 第一份推理:先跑官方小模型,再理解输入输出

用固定猫图和S3版MobileNetV2核对输入身份、RGB归一化、Top5分数与资源记录;实际构建已通过,实板分类、耗时与内存待验证。

读完能做什么
能复现同一输入和模型的构建,把解码、装载、推理与分数解释分开记录。
开始之前
先完成V01板型/原生USB与E07组件锁,能保存同次源码、锁、配置和ELF;按E09备份原Flash后才刷入。
所需器材
官方ESP32-S3-EYE-MB_V2.2;8MiB Flash与8MiB OPI PSRAM,可靠Micro-USB数据线与电脑USB供电、已安装ESP-IDF5.5.5/GCC14.2.0环境;主机Python3.12/Pillow12.3.0独立venv、原固件Flash布局及完整备份;不接电池、外部器件或负载
版本组合
框架/工具:IDF5.5.5 b774170… / GCC14.2.0 esp-14.2.0_20260121模型组件:esp-dl3.2.0 dc380d4… / imagenet_cls0.3.0 / dl_fft0.8.0模型/图片:S3 MobileNetV2 int8 3,611,648B / JPEG300×300 35,952B;两项SHA配置门禁实际构建:app4,984,368B含RODATA模型;17保留产物,桌面Pillow12.3.0
适用范围

S3-EYE-MB_V2.2 / 8MiB DIO80+8MiB OPI80 / native USB19/20;不初始化相机/SD/LCD/麦克风/RF。仅实际构建和桌面固定文件检查,物理推理/资源/电气待验证。

这篇验证到了哪一步

资料核对已通过
docs/esp32-evidence/vision-inference-sources.json:固定来源、两项输入SHA与组件安装核对。验证日期:
构建 / 配置已通过
docs/esp32-evidence/vision-inference-builds.json:实际Exit0/输入稳定/17产物;非板端运行。对象:esp32s3 / S3-EYE-MB_V2.2版本:IDF5.5.5 / esp-dl3.2.0验证日期:
仿真已通过
docs/esp32-evidence/vision-inference-host.json:固定JPEG/模型身份与解码检查,不执行MobileNet。对象:actual host Pillow/file checks验证日期:
本站实板待验证
无指定S3-EYE实板推理/图像/heap/时延/电源记录,CSV只有表头。

构建通过只说明指定环境中的代码或配置通过检查;实物功能、功耗和稳定性以对应的实板记录为准。

01 / 固定输入推理实验

先用固定图片排除相机变量

第一次推理先不接业务。用官方猫图和S3版MobileNetV2 int8 ImageNet模型,核文件身份、预处理、分配和结果格式;工程不初始化摄像头、LCD、SD、麦克风或Wi-Fi。相机仍装在S3-EYE上,但没有抓现实画面。

本站完成实际IDF构建和桌面文件检查,没有实板推理类别、heap或耗时。原计划的“输出资源记录”由完整串口字段和空记录表承接,读者须在指定板上保存真实结果。

参考:[1] ESP-DL v3.2.0官方MobileNetV2完整入口;本例移除SD/BSP路径[2] imagenet_cls固定包装器:预处理mean/std、默认模型与装载[7] 固定cat.jpg与项目许可

02 / 固定输入推理实验

板修订、PSRAM和工具链一次锁清

固定ESP32-S3-EYE-MB_V2.2,8MiB Flash与8MiB Octal PSRAM;仅Micro-USB原生Serial/JTAG19/20和电脑USB供电,没有UART桥,不接电池或外部器件。所有排线检查先断电,不套用DevKitC桥口说明。

复用IDF5.5.5,标准Registry依赖esp-dl3.2.0、imagenet_cls0.3.0、dl_fft0.8.0;实际传递锁esp-dsp1.7.0与esp_new_jpeg0.6.1。没有私有override或BSP。Flash8MiB/DIO80、OPI PSRAM80、CPU240MHz、main栈16KiB、原生USB控制台。

参考:[6] 模型选择/RODATA位置Kconfig及组件版本[8] S3-EYE v2.2板、原生USB与恢复指南[9] IDF5.5.5 PSRAM容量API;与heap剩余量区分

03 / 固定输入推理实验

固定的是模型与JPEG的实际字节

main/CMakeLists配置阶段同时核35952B猫图和组件中3611648B的S3模型SHA,版本一样也不能跳过文件检查。模型由固定组件取回,不把P4版本换名放进来。构建把实际SHA编入日志常量,这是构建时门禁,不是设备启动后重新校验Flash。

JPEG为300×300。Pillow12.3.0实际打开/解码通过并得到270000B RGB888;桌面解码不证明ESP解码逐像素相同,也没有执行MobileNet。

输入SHA256
cat.jpg / 35,952B71a56b94233940cfc942457f3413ed248b847db4110eced52768852d87320508
S3 .espdl / 3,611,648B59124aace8bd5cd9e502b938b7d90c9f0949949ee0087b3841911f6a785180db

参考:[1] ESP-DL v3.2.0官方MobileNetV2完整入口;本例移除SD/BSP路径[6] 模型选择/RODATA位置Kconfig及组件版本[7] 固定cat.jpg与项目许可

04 / 固定输入推理实验

RGB、归一化与int8不是可互换步骤

固定包装器对RGB888整图缩放为224×224×3,默认不启用letterbox;每通道按mean[123.675,116.28,103.53]和std[58.395,57.12,57.375]归一化,再按模型指数转为signed int8。直接送0..255、把RGB当BGR或混用模型都会改变问题。

S3包装器的RGB565_BIG_ENDIAN是转换能力参数,本例实际传RGB888,不额外改字节序。换真实摄像帧以后仍要单独核曝光、清晰度、裁剪和颜色,不能用本页固定图替代。

参考:[2] imagenet_cls固定包装器:预处理mean/std、默认模型与装载[5] 固定图像预处理:形状/量化指数与默认非letterbox

05 / 固定输入推理实验

构建与实体刷入分开留证

完整下载给出独立构建目录命令与原生USB端口恢复步骤。首次刷分区前按E09保存原8MiB完整Flash、原布局和SHA;本工程只有factory,不能当原项目OTA包。使用同次flasher_args.json,不手拼错误偏移。

实际本机构建Exit0,10项宽项目预输入包含工具/配置,只有一个实际应用C++编译单元;17件保留产物含app、ELF/map、配置、编译数据库、分区、模型和锁。没有把10项都称编译单元,也没有把构建输出的刷写提示当已刷板。

  1. 先核板背MB_V2.2、8MiB OPI与可靠USB数据线。
  2. 在已安装IDF5.5.5终端从工程根按README build,保存锁、源与同次ELF/config。
  3. 核备份和端口后才自行flash monitor;Ctrl+]退出,原生USB重枚举后重新核端口。
做到这里,你应该看到
实际串口应依次有IMAGE/MODEL_SHA256、DECODE、MODEL_LOAD、三次RUN/TOP及END;本站未提供虚构成功log。

参考:[1] ESP-DL v3.2.0官方MobileNetV2完整入口;本例移除SD/BSP路径[6] 模型选择/RODATA位置Kconfig及组件版本[8] S3-EYE v2.2板、原生USB与恢复指南

06 / 固定输入推理实验

app已包含模型,不能重复加一次

本次实际app4,984,368B已包含3,611,648B RODATA模型。factory为0x6f0000B,板Flash8MiB,bootloader/分区表/NVS等另占;不把模型再加到app上算占用,也不把静态文件大小当RAM。

MEM分别观察internal和PSRAM的free/largest。物理PSRAM容量用esp_psram_get_size核,剩余heap可能小于标称容量;free不等于最大可分配连续块。资源不足时上游new/assert可能终止,先保存panic与同次ELF,不把结束free近似恢复称泄漏已排除。

参考:[3] 固定分类基类:pre/model/post及lazy_load[9] IDF5.5.5 PSRAM容量API;与heap剩余量区分

07 / 固定输入推理实验

解码、装载和完整RUN各计自己的时间

JPEG解码单独计DECODE;classifier构造显式lazy_load=false,MODEL_LOAD含装载/分配。之后三次RUN包围preprocess→model→postprocess,排除前面的JPEG解码和构造;串口打印发生在计时结束后。

同图三次不是基准测试、摄像FPS或硬实时保证,调度/缓存/优化仍影响结果。上游示例中的9175/1281016/1140µs只是其参考值,本页不把它们填进本站空表。

参考:[1] ESP-DL v3.2.0官方MobileNetV2完整入口;本例移除SD/BSP路径[3] 固定分类基类:pre/model/post及lazy_load

08 / 固定输入推理实验

Softmax Top5不是这张图的准确率

固定后处理对1000类输出先Softmax,再排序取五项;本例保留构造器默认top5和最低阈值,不调用阈值setter。五项和通常小于1,因为未显示其他995类。程序核五项、有限0..1和降序,只是输出格式门禁。

Top1总会有一个,包括模糊、遮挡或域外图片;分数不是校准概率、未知类检测或模型准确率。要判断是否“识别正确”,需独立标签和混淆矩阵,按V05继续;不能把类别名直接变成执行器命令。

参考:[2] imagenet_cls固定包装器:预处理mean/std、默认模型与装载[4] 固定后处理:Softmax与排序/TopK[10] ESP-DL固定量化说明:S3 ROUND_HALF_UP与PTQ验收

09 / 固定输入推理实验

完整入口:固定输入与三次资源观察

下面是下载中的完整main;显式模型类型、非lazy装载、三个完整RUN和资源字段与实际构建源相同。摄像头/无线没有初始化,实体指标仍待运行。

代码 / 配置
// ---- main/main.cpp ----
/* SPDX-License-Identifier: MIT */
#include <cmath>
#include <cinttypes>
#include <cstdio>
#include "dl_image_jpeg.hpp"
#include "imagenet_cls.hpp"
#include "esp_heap_caps.h"
#include "esp_psram.h"
#include "esp_timer.h"
#include "esp_system.h"

extern const uint8_t image_start[] asm("_binary_cat_jpg_start");
extern const uint8_t image_end[] asm("_binary_cat_jpg_end");
static void memory(const char *phase) {
    printf("MEM,%s,internal_free=%u,internal_largest=%u,psram_free=%u,psram_largest=%u\n",phase,
        (unsigned)heap_caps_get_free_size(MALLOC_CAP_INTERNAL|MALLOC_CAP_8BIT),
        (unsigned)heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL|MALLOC_CAP_8BIT),
        (unsigned)heap_caps_get_free_size(MALLOC_CAP_SPIRAM|MALLOC_CAP_8BIT),
        (unsigned)heap_caps_get_largest_free_block(MALLOC_CAP_SPIRAM|MALLOC_CAP_8BIT));
}
extern "C" void app_main(void) {
    printf("V03,software_fixed_image_only,idf=%s\nIMAGE_SHA256,%s\nMODEL_SHA256,%s\n",
           esp_get_idf_version(),V03_IMAGE_SHA256,V03_MODEL_SHA256);
    memory("before_decode");
    if(!esp_psram_is_initialized()||esp_psram_get_size()!=8U*1024*1024) {
        printf("STOP,8MiB_OPI_PSRAM_required; no inference attempted\n");return;
    }
    const int64_t began=esp_timer_get_time();
    dl::image::jpeg_img_t jpeg={.data=(void*)image_start,.data_len=(size_t)(image_end-image_start)};
    auto image=dl::image::sw_decode_jpeg(jpeg,dl::image::DL_IMAGE_PIX_TYPE_RGB888);
    if(!image.data||image.width!=300||image.height!=300) {
        printf("STOP,decode_or_fixed_image_dimensions\n");heap_caps_free(image.data);return;
    }
    printf("DECODE,image_rgb888=%ux%u,us=%" PRId64 "\n",(unsigned)image.width,(unsigned)image.height,esp_timer_get_time()-began);
    memory("decoded");
    const int64_t load_begin=esp_timer_get_time();
    /* Explicit type; false loads now so construction is excluded from each run. */
    auto *classifier=new ImageNetCls(ImageNetCls::MOBILENETV2_S8_V1,false);
    printf("MODEL_LOAD,us=%" PRId64 "\n",esp_timer_get_time()-load_begin);
    memory("loaded");
    for(unsigned run=0;run<3;run++) {
        const int64_t start=esp_timer_get_time();
        const auto &results=classifier->run(image);
        const int64_t elapsed=esp_timer_get_time()-start;
        printf("RUN,%u,pipeline_us=%" PRId64 ",topk=%u\n",run,elapsed,(unsigned)results.size());
        bool valid=results.size()==5;float previous=2.0f;
        for(size_t i=0;i<results.size();i++) {
            const auto &result=results[i];
            valid=valid&&std::isfinite(result.score)&&result.score>=0&&result.score<=1&&result.score<=previous;
            previous=result.score;
            printf("TOP,%u,%u,label=%s,softmax_score=%.9f\n",run,(unsigned)i+1,result.cat_name,result.score);
        }
        if(!valid){printf("STOP,invalid_top5_contract\n");break;}
        memory("after_run");
    }
    delete classifier;heap_caps_free(image.data);memory("released");
    printf("END,no_camera_no_RF; score_is_not_accuracy_or_calibrated_probability\n");
}

参考:[1] ESP-DL v3.2.0官方MobileNetV2完整入口;本例移除SD/BSP路径[2] imagenet_cls固定包装器:预处理mean/std、默认模型与装载[3] 固定分类基类:pre/model/post及lazy_load[9] IDF5.5.5 PSRAM容量API;与heap剩余量区分

10 / 固定输入推理实验

完整构建身份门禁和桌面检查器

CMake只允许这一组固定输入;桌面脚本检查图片/模型文件身份、Pillow解码、尺寸与EDL2头。脚本的通过不代表算子运行、正确类别、PSRAM或实板时间。首次组件解析后才有managed_components中的模型路径。

代码 / 配置
// ---- main/CMakeLists.txt ----
idf_component_register(SRCS "main.cpp" INCLUDE_DIRS "."
    REQUIRES imagenet_cls esp_timer heap esp_psram
    EMBED_FILES "cat.jpg")
file(SHA256 "${CMAKE_CURRENT_LIST_DIR}/cat.jpg" fixed_jpeg_sha)
if(NOT fixed_jpeg_sha STREQUAL "71a56b94233940cfc942457f3413ed248b847db4110eced52768852d87320508")
    message(FATAL_ERROR "V03 fixed upstream cat.jpg hash mismatch; retain input and inspect source")
endif()
idf_component_get_property(imagenet_dir espressif__imagenet_cls COMPONENT_DIR)
set(model "${imagenet_dir}/models/s3/imagenet_cls_mobilenetv2_s8_v1.espdl")
file(SHA256 "${model}" fixed_model_sha)
if(NOT fixed_model_sha STREQUAL "59124aace8bd5cd9e502b938b7d90c9f0949949ee0087b3841911f6a785180db")
    message(FATAL_ERROR "V03 fixed upstream S3 model hash mismatch; do not relabel another model")
endif()
target_compile_definitions(${COMPONENT_LIB} PRIVATE
    V03_IMAGE_SHA256="${fixed_jpeg_sha}" V03_MODEL_SHA256="${fixed_model_sha}")

# ---- tools/check_inputs.py ----
#!/usr/bin/env python3
# SPDX-License-Identifier: MIT
"""Check the exact two trusted inputs. Pillow decode is a host check, not ESP inference."""
import argparse,hashlib,json,pathlib
IMAGE_SHA='71a56b94233940cfc942457f3413ed248b847db4110eced52768852d87320508'
MODEL_SHA='59124aace8bd5cd9e502b938b7d90c9f0949949ee0087b3841911f6a785180db'
def exact(path,size,digest):
    data=pathlib.Path(path).read_bytes()
    if len(data)!=size or hashlib.sha256(data).hexdigest()!=digest:raise ValueError('fixed input size/SHA mismatch: '+str(path))
    return data
def check(image,model=None):
    data=exact(image,35952,IMAGE_SHA)
    from PIL import Image,__version__
    with Image.open(image)as im:
        im.load()
        if im.format!='JPEG'or im.size!=(300,300):raise ValueError('fixed JPEG format/dimensions')
        rgb=im.convert('RGB');rgb_sha=hashlib.sha256(rgb.tobytes()).hexdigest()
    result=dict(imageSHA256=IMAGE_SHA,jpegBytes=len(data),dimensions=[300,300],hostRGB888Bytes=270000,hostRGB888SHA256=rgb_sha,pillow=__version__,scope='Host Pillow decoding and file identity only; does not execute ESP-DL, equal ESP JPEG pixels, measure latency, classify or prove board PSRAM.')
    if model is not None:
        data=exact(model,3611648,MODEL_SHA)
        if data[:4]!=b'EDL2':raise ValueError('expected fixed EDL2 package header')
        result.update(modelSHA256=MODEL_SHA,modelBytes=len(data),modelHeader='EDL2')
    return result
def main():
    root=pathlib.Path(__file__).resolve().parents[1]
    p=argparse.ArgumentParser();p.add_argument('--image',type=pathlib.Path,default=root/'main/cat.jpg');p.add_argument('--model',type=pathlib.Path);a=p.parse_args()
    try:result=check(a.image,a.model)
    except(OSError,ValueError)as error:p.error(str(error))
    print(json.dumps(result,indent=2))
if __name__=='__main__':main()

参考:[6] 模型选择/RODATA位置Kconfig及组件版本[7] 固定cat.jpg与项目许可

11 / 固定输入推理实验

失败先留原件,再改变一个条件

SHA失败先保留下载文件、dependencies.lock和错误,核模型/组件/目标,不删除门禁。启动停在PSRAM先核板修订/OPI80/供电与原始log,不切成QIO碰运气;类别异常先查固定输入、RGB和归一化。

records/inference-observations.csv只有表头。真实板卡、分类、资源、时延和供电仍待验证;新应用/说明MIT,官方JPEG与模型保留上游MIT及组件各自许可证,下载不捆绑SDK/managed_components。接着做二维码状态机或自有小模型,保留每一层的失败记录。

参考:[2] imagenet_cls固定包装器:预处理mean/std、默认模型与装载[5] 固定图像预处理:形状/量化指数与默认非letterbox[6] 模型选择/RODATA位置Kconfig及组件版本[8] S3-EYE v2.2板、原生USB与恢复指南

BACK TO THE SOURCE

资料来源与版本说明

实际读固定包装器/基类/预处理/后处理与Kconfig、官方图/许可和同V01板修订;来源/安装cmp和构建保留记录详见证据。资料核对日期:。资料核对、程序构建与实板测试分别记录,不互相替代。

  1. ESP-DL v3.2.0官方MobileNetV2完整入口;本例移除SD/BSP路径
  2. imagenet_cls固定包装器:预处理mean/std、默认模型与装载
  3. 固定分类基类:pre/model/post及lazy_load
  4. 固定后处理:Softmax与排序/TopK
  5. 固定图像预处理:形状/量化指数与默认非letterbox
  6. 模型选择/RODATA位置Kconfig及组件版本
  7. 固定cat.jpg与项目许可
  8. S3-EYE v2.2板、原生USB与恢复指南
  9. IDF5.5.5 PSRAM容量API;与heap剩余量区分
  10. ESP-DL固定量化说明:S3 ROUND_HALF_UP与PTQ验收