尧图精选

OpenCV 读 YOLO-V8 Onnx 的 output 维度和坐标对不上?用 TaoToken 接 Codex 查 readNetFromONNX

🕒 发布时间:2026/9/17 11:06:11 📁 来源:尧图网络
用 OpenCV 的 cv2.dnn.readNetFromONNX 读 ultralytics 导出的 YOLO-V8 Onnx 人脸检测模型 best.onnx最常见的翻车不是读不进来而是 net.forward() 的 output 维度和你后处理里写的 transpose、score、class_id 对不上框画到天上或者置信度全是 0。先别急着改 NMSBoxes。打开 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 创建一把 Key用 TaoToken 把 Codex 接上让它在你的本机对着 best.onnx 输出张量和 readNetFromONNX 这段代码逐项核对。TaoToken 在这里只负责给 Codex 供 Key 和 Base URL不碰你的模型文件也不替你执行 OpenCV 脚本。你需要做的是在本地把 raw output 的 shape、min/max、前几行打印出来把 ultralytics 的 export 命令和你的后处理代码一起贴给 Codex让它判断问题出在模型导出参数还是出在 transpose、坐标换算、NMS 这几步。下面按排障顺序走一遍不按通识教程的“先装环境再跑 demo”走。手里有 best.pt、best.onnx、一张人脸图和一个把框画歪的脚本就够开始了。1. output.shape 是 (1,5,8400) 还是 (1,8400,5)先别急着 transpose1.1 readNetFromONNX 之后先把原始输出打出来原文的流程是 cv2.dnn.readNetFromONNX 加载 best.onnx然后 blobFromImage、net.setInput、net.forward接着立刻output.transpose((0, 2, 1))。但不同导出参数、不同 OpenCV 版本、不同 ultralytics 版本输出布局可能不一样。你如果直接抄了某一段后处理最容易错的就是把已经转过的张量再转一次。先在本地跑一段只打印、不画框的代码把 raw output 的真实形状拿到手import cv2 import numpy as np onnx_model_path runs/detect/train/weights/best.onnx img_path data/images/10.jpg input_shape (640, 640) net cv2.dnn.readNetFromONNX(onnx_model_path) img cv2.imread(img_path) blob cv2.dnn.blobFromImage( img, 1 / 255.0, input_shape, swapRBTrue, cropFalse ) net.setInput(blob) raw net.forward() print(raw.shape , raw.shape) print(raw.dtype , raw.dtype) print(raw.min / raw.max , raw.min(), raw.max()) if raw.ndim 3: print(第 0 个 batch 前 5 列) print(raw[0, :, :5])如果自定义人脸检测只有一个类别常见输出是(1, 5, 8400)其中 5 表示 4 个框参数加 1 个类别分数8400 是 640x640 输入下的候选框数量。也可能因为导出方式不同看到(1, 8400, 5)。把这段打印结果原样贴给 Codex比贴一张报错截图有用得多。1.2 用 Codex 判断该不该 transpose((0,2,1))拿到 raw.shape 之后判断规则并不复杂但人在排障时容易脑子发热。你可以让 Codex 按下面这几条帮你确认如果raw.shape[1] 4 类别数而raw.shape[2]远大于raw.shape[1]那它大概率是(1, 5, 8400)需要raw.transpose(0, 2, 1)变成(1, 8400, 5)。如果raw.shape[2] 4 类别数而raw.shape[1]远大于raw.shape[2]那它已经是(1, 8400, 5)不要再 transpose。如果不确定打印raw[0, 0, :5]和raw[0, :5, 0]看前四个数字像不像中心点加宽高。YOLOv8 默认输出通常是cx, cy, w, h, 类别分数...不是左上角加右下角。把 raw.shape、几行样本值和你的 transpose 那一行一起交给 Codex让它先给结论不要让它直接改一大段代码。排障阶段最怕一次改十个地方最后连哪一步改对了都不知道。1.3 直接套网上代码为什么把框画飞如果 raw 实际是(1, 8400, 5)你又执行了transpose((0, 2, 1))张量会变成(1, 5, 8400)。后面的循环for i in range(output[0].shape[0])只会跑 5 次每次拿到一整行 8400 个数当作一个 boxNMSBoxes 的输入完全变形。表现就是要么一个框都不出要么框的位置和大小离谱要么 score 看起来很大但类别完全不对。另一种常见情况是 raw 是(1, 5, 8400)你转成了(1, 8400, 5)但取 score 时仍然用box[4:][class_id]而 class_id 来自cv2.minMaxLoc(box[4:])的max_idx[1]。一维数组里这个索引通常能用但一旦形状变成二维max_idx[1]的含义就变了。让 Codex 对照 raw.shape 和你的切片方式能很快看出是 transpose 多了还是索引写错了。2. best.pt 导出 best.onnx 时imgsz、opset、dynamic 会怎样影响后处理2.1 ultralytics export 命令里的三个关键参数原文用model.export(formatonnx, imgsz[640,640], opset12)把自定义人脸检测 best.pt 转成 best.onnx。这里真正会影响你后处理的主要是三个地方imgsz[640,640]决定输出候选框数量。640 输入下80x80、40x40、20x20 三个尺度的网格加起来是 8400。如果你换成 320候选数量会变成 2100。后处理里写死 8400 就会出问题。opset12影响算子集合。大多数情况下它不改变输出布局但不同 OpenCV 版本对某些算子的支持程度不同读模型时可能报错或者推理结果有细微差异。dynamic如果打开输出 shape 可能变成动态维度。OpenCV 的 dnn 模块对动态 shape 的支持不如静态 shape 稳。排障时建议先用固定imgsz导出把问题缩小到后处理再考虑动态输入。你可以把导出命令改成显式命名方便对照from ultralytics import YOLO face_model YOLO(runs/detect/train/weights/best.pt) face_model.export( formatonnx, imgsz[640, 640], opset12, dynamicFalse, simplifyFalse, )导出的文件仍然放在runs/detect/train/weights/best.onnx附近。先不要同时改simplify、half、dynamic三个参数否则你很难判断是哪一个影响了输出。2.2 blobFromImage 的尺寸和 x_factor/y_factor 要对齐OpenCV 这边blobFromImage(img, 1/255.0, input_shape, swapRBTrue, cropFalse)会把原图缩放到input_shape。原文里input_shape (640, 640)所以坐标还原时height, width, _ img.shape x_factor width / input_shape[0] y_factor height / input_shape[1]这里最容易错的是把 x_factor 和 y_factor 写反或者输入尺寸用了 640导出时却用了其他 imgsz。还有一个更隐蔽的问题有些预处理会做 letterbox保持长宽比并补灰边blobFromImage 直接 resize 则不补边图像会被拉伸。两者坐标还原公式不同。如果你后面改成 letterbox框会整体偏移尤其是图片边缘的人脸。排障时先保持和原文一致的直接 resize把 transpose 和 score 问题解决再换 letterbox。让 Codex 对照input_shape、x_factor、y_factor和blobFromImage那一行检查尺寸链路是否一致。2.3 在模型广场确认 Codex 能用的模型 IDCodex 要接到 TaoTokenKey 和模型 ID 都得从官网这边拿。打开 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 注册后进入控制台创建 API Key再去模型广场看当时可用的模型 ID。不要凭记忆写gpt-5或者随手加日期后缀模型 ID 以模型广场当时列表为准。拿到 Key 之后先记下两件事官网落地页用于注册、创建 Key、看模型广场、看用量https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end填进 Codex 的 Base URL 是https://taotoken.net/api末尾不要带/v1也不要加 UTM 参数。这两类地址不要混。官网链接是给人点的Base URL 是填进配置文件的。把?utm_source...加到 Base URL 上Codex 请求路径会直接错。3. 把 Codex 的 Base URL 指到 TaoTokenconfig.toml 与 API Key3.1 打开官网创建 Key 并复制到本地打开 TaoToken完成注册登录进入控制台创建 API Key。复制出来的值先不要贴到聊天窗口也不要提交到 Git。本文所有示例都用占位符YOUR_API_KEY你本地替换成自己的 Key 即可。同时看一眼控制台里的用量页面确认这个 Key 对应的模型权限。Codex 只是一层代码审查工具它不会帮你运行 OpenCV也不会替你把 best.onnx 跑一遍。它需要的是你贴给它 raw output 的 shape、几行样本值、导出命令、后处理代码然后它给出修改建议。3.2 ~/.codex/config.toml 的最小可用配置Codex 的配置文件通常放在~/.codex/config.toml。先设置环境变量再写 provider 配置。不要套用 Claude Code 的ANTHROPIC_*变量Codex 不吃这一套。环境变量可以这样设export TAOTOKEN_API_KEYYOUR_API_KEYWindows PowerShell 下$env:TAOTOKEN_API_KEYYOUR_API_KEY然后编辑~/.codex/config.tomlmodel YOUR_MODEL_ID model_provider taotoken [model_providers.taotoken] name TaoToken base_url https://taotoken.net/api env_key TAOTOKEN_API_KEY wire_api chat这里的YOUR_MODEL_ID从模型广场获取YOUR_API_KEY从控制台创建。保存后启动codex如果它问你使用哪个 provider选taotoken对应的配置。若报 401先检查环境变量是否在当前终端生效若报模型不存在回到模型广场确认 ID 拼写。Base URL 始终是https://taotoken.net/api不要写成/api/v1。3.3 用“只诊断不执行”的提示词让 Codex 查 readNetFromONNX把下面这段提示词改成你自己的信息贴给 Codex我在用 OpenCV 读 YOLO-V8 Onnx 人脸检测模型。请只做代码审查不要执行任何模型或脚本。 依赖版本 ultralytics8.1.37 opencv-python4.7.0.68 onnx1.16.1 导出命令 face_model.export(formatonnx, imgsz[640,640], opset12, dynamicFalse) 我本地打印的 raw output raw.shape ... raw.min / raw.max ... 前几行样本 ... 我的后处理代码 把 output.transpose、score、class_id、x_factor、y_factor、NMSBoxes 那段贴上来 请逐项回答 1. 这个 raw.shape 该不该 transpose((0,2,1)) 2. score 和 class_id 该怎么取 3. x_factor、y_factor 是否和 blobFromImage 对齐 4. NMSBoxes 的 boxes、scores、threshold 有没有格式问题 5. 最可能的一处错误在哪里这样问的好处是Codex 不会泛泛地给你写一篇 YOLO 教程而是围绕你手里的 best.onnx 输出做排障。跑通一次请求后你就能判断问题是在模型导出参数还是在 OpenCV 后处理解析。4. 逐项排查 score、class_id、x_factor/y_factor 和 NMSBoxes4.1 score 和 class_id 取法从 cv2.minMaxLoc 换成 np.argmax原文里用cv2.minMaxLoc(box[4:])取类别分数再用max_idx[1]当 class_id。对一维数组来说这能跑但可读性和稳定性都不如np.argmax。排障时建议改成下面这种cls_scores row[4:] class_id int(np.argmax(cls_scores)) score float(cls_scores[class_id]) if score threshold: continue如果你只有一个类别faceclass_id 应该始终是 0。若打印出来 class_id 经常大于 0说明row[4:]取错了或者 transpose 后每行长度不对。把这个打印结果贴给 Codex它能很快判断是不是行切片错了。4.2 坐标还原xywh 中心点格式与 xyxy 格式别混YOLOv8 默认输出的是中心点加宽高也就是cx, cy, w, h。原文的还原方式是x int((cx - 0.5 * w) * x_factor) y int((cy - 0.5 * h) * y_factor) w int(w * x_factor) h int(h * y_factor)这套公式在 raw 输出为(1, 8400, 5)且row[:4]是cx, cy, w, h时成立。如果你导出时带了nmsTrue或者换成了某些会直接输出xyxy的后端就不能再减半个宽高。判断方法很简单打印前几行看前四个数是否满足x1 x2、y1 y2的框语义。中心点格式里cx和w之间没有严格的左上右下关系但w、h通常大于 0 且不会横跨整张图。另一个常见错误是x_factor和y_factor用反。宽对应 x高对应 ywidth / 640给 xheight / 640给 y。写反之后人脸框会像被斜着拉伸和模型本身无关。4.3 NMSBoxes 的输入格式和 indexes 兼容NMSBoxes 需要boxes是[x, y, w, h]格式scores是 Python float 列表。原文把 numpy 的 score 直接 append 进去某些 OpenCV 版本会报类型错误。更稳的写法boxes.append([x, y, w, h]) scores.append(float(score)) classifys.append(model_classify[class_id])调用时indexes cv2.dnn.NMSBoxes(boxes, scores, 0.25, 0.45) indexes np.array(indexes).flatten()OpenCV 4.7 返回的indexes有时是 tuple有时是二维数组直接for i in indexes可能拿到数组而不是整数。flatten()之后再用int(i)取。NMS 的 score 阈值和前面筛选阈值可以不同但不要混在一个变量里。排障时先把threshold设为 0.3NMS 的 score 阈值也设 0.25看框能不能出来再慢慢调。5. 跑一张人脸图验证从 raw output 到画框5.1 可复制的 OpenCV 排障脚本下面这段把打印和画框放在一起但先打印再画框。你可以直接替换路径运行import cv2 import numpy as np onnx_model_path runs/detect/train/weights/best.onnx img_path data/images/10.jpg input_shape (640, 640) model_classify [face] net cv2.dnn.readNetFromONNX(onnx_model_path) img cv2.imread(img_path) height, width img.shape[:2] blob cv2.dnn.blobFromImage( img, 1 / 255.0, input_shape, swapRBTrue, cropFalse ) net.setInput(blob) raw net.forward() print(raw.shape , raw.shape) print(raw.min / raw.max , raw.min(), raw.max()) if raw.ndim ! 3: raise ValueError(f不认识的 raw 维度{raw.shape}) # 根据输出布局决定是否 transpose if raw.shape[2] 4 len(model_classify): pred raw[0] elif raw.shape[1] 4 len(model_classify): pred raw.transpose(0, 2, 1)[0] else: raise ValueError(f请把 raw.shape 贴给 Codex{raw.shape}) print(pred.shape , pred.shape) x_factor width / input_shape[0] y_factor height / input_shape[1] boxes, scores, class_ids [], [], [] threshold 0.3 for row in pred: cls_scores row[4:] class_id int(np.argmax(cls_scores)) score float(cls_scores[class_id]) if score threshold: continue cx, cy, bw, bh row[:4] x int((cx - 0.5 * bw) * x_factor) y int((cy - 0.5 * bh) * y_factor) bw int(bw * x_factor) bh int(bh * y_factor) boxes.append([x, y, bw, bh]) scores.append(score) class_ids.append(class_id) print(筛选后候选框数量 , len(boxes)) indexes cv2.dnn.NMSBoxes(boxes, scores, 0.25, 0.45) indexes np.array(indexes).flatten() for i in indexes: i int(i) x, y, w, h boxes[i] label f{model_classify[class_ids[i]]}: {scores[i]:.2f} cv2.rectangle(img, (x, y), (x w, y h), (0, 255, 0), 2) cv2.putText( img, label, (x, y - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2 ) cv2.imshow(best.onnx result, img) cv2.waitKey(0) cv2.destroyAllWindows()先看打印的raw.shape、pred.shape和候选框数量再看窗口里的框。如果候选框数量是 0不是 NMS 的问题而是 score 全低于 threshold或者 class_id 取错。如果候选框很多但框全错优先查 transpose 和坐标还原。5.2 把打印结果贴回 Codex 的提问模板运行完上面脚本后把这三类信息贴回 Codex导出命令formatonnx, imgsz[640,640], opset12, dynamicFalse张量信息raw.shape、pred.shape、raw.min()、raw.max()、pred[:3]后处理代码transpose、score、class_id、x_factor、y_factor、NMSBoxes然后问它一个很具体的问题“请判断 pred 的每一行是cx,cy,w,h,score还是其他布局并指出我代码里最可能错的一行。” 不要问“YOLOv8 怎么用 OpenCV 推理”那会得到一篇通用教程和你的 best.onnx 关系不大。Codex 给出建议后一次只改一处先改 transpose再跑再改 score/class_id再跑最后改坐标和 NMS。这样每改一次你都能从打印结果看到变化。如果改完框还是不对回到 raw output检查是不是imgsz、input_shape、x_factor、y_factor这条链路某一环没对齐。6. 跑通之后去控制台对一下这次 Codex 调用6.1 模型对话里先确认模型 ID 和 Base URL配置保存后先在 TaoToken 模型对话 里用同一把 Key 发一条测试消息确认模型 ID 和 Base URL 没填错。这一步能排除 Codex 配置本身的问题避免你把 401 当成 OpenCV 报错去查。如果你后面要长期用 Codex 查 best.onnx 的后处理可以打开 Coding Plan 看套餐是否够用。Key 在 控制台 API Keys 创建和管理。若你同时也接 Claude Code环境变量对照见 接入文档。6.2 本篇 best.onnx 排障最常见的几类错第一类是raw.shape判断错。(1,5,8400)和(1,8400,5)两种布局对应不同的 transpose 分支写错之后循环次数和每行长度都会错。第二类是 score/class_id 取错。box[4:]如果取到坐标np.argmax会给出荒唐的类别索引model_classify[class_id]可能直接越界。第三类是x_factor、y_factor写反或用了导出时的 imgsz 之外的尺寸框会整体拉伸或偏移。第四类是 NMSBoxes 的 boxes 不是xywh或者 scores 不是 float 列表OpenCV 在 4.7 版本下可能直接抛类型错误。把这几类错分开验证不要一起改。你可以在本地把 raw output 前几行、pred.shape、候选框数量、NMS 前后数量都打印出来再贴给 Codex 做一次交叉检查。Codex 只做生成、解释和对照不直接连你的本地模型执行真正的打印、运行、画框步骤仍然在你机器上完成。当你把 raw.shape、export 命令和后处理代码同时贴给 Codex通常一两轮就能定位到是 transpose 多写了一次还是 x_factor/y_factor 用反。改完在本机重跑框一旦回到人脸上再回 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 看这次调用有没有记上顺手把控制台里的 Key 备注成 best.onnx 排障专用下次切模型不容易混。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →