先用固定图片排除相机变量
第一次推理先不接业务。用官方猫图和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与项目许可
板修订、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剩余量区分
固定的是模型与JPEG的实际字节
main/CMakeLists配置阶段同时核35952B猫图和组件中3611648B的S3模型SHA,版本一样也不能跳过文件检查。模型由固定组件取回,不把P4版本换名放进来。构建把实际SHA编入日志常量,这是构建时门禁,不是设备启动后重新校验Flash。
JPEG为300×300。Pillow12.3.0实际打开/解码通过并得到270000B RGB888;桌面解码不证明ESP解码逐像素相同,也没有执行MobileNet。
| 输入 | SHA256 |
|---|---|
| cat.jpg / 35,952B | 71a56b94233940cfc942457f3413ed248b847db4110eced52768852d87320508 |
| S3 .espdl / 3,611,648B | 59124aace8bd5cd9e502b938b7d90c9f0949949ee0087b3841911f6a785180db |
参考:[1] ESP-DL v3.2.0官方MobileNetV2完整入口;本例移除SD/BSP路径[6] 模型选择/RODATA位置Kconfig及组件版本[7] 固定cat.jpg与项目许可
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
构建与实体刷入分开留证
完整下载给出独立构建目录命令与原生USB端口恢复步骤。首次刷分区前按E09保存原8MiB完整Flash、原布局和SHA;本工程只有factory,不能当原项目OTA包。使用同次flasher_args.json,不手拼错误偏移。
实际本机构建Exit0,10项宽项目预输入包含工具/配置,只有一个实际应用C++编译单元;17件保留产物含app、ELF/map、配置、编译数据库、分区、模型和锁。没有把10项都称编译单元,也没有把构建输出的刷写提示当已刷板。
- 先核板背MB_V2.2、8MiB OPI与可靠USB数据线。
- 在已安装IDF5.5.5终端从工程根按README build,保存锁、源与同次ELF/config。
- 核备份和端口后才自行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与恢复指南
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剩余量区分
解码、装载和完整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
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验收
完整入口:固定输入与三次资源观察
下面是下载中的完整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剩余量区分
完整构建身份门禁和桌面检查器
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()
失败先留原件,再改变一个条件
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与恢复指南
资料来源与版本说明
实际读固定包装器/基类/预处理/后处理与Kconfig、官方图/许可和同V01板修订;来源/安装cmp和构建保留记录详见证据。资料核对日期:。资料核对、程序构建与实板测试分别记录,不互相替代。