OpenCV 报错 Assertion failed (s >= 0) in cv::setSize 的完整解决方案
- 在使用 OpenCV 进行图像处理或深度学习推理时,遇到过这样一个令人困惑的错误:
xml
OpenCV(4.10.0) Error: Assertion failed (s >= 0) in cv::setSize,
file ...\opencv\modules\core\src\matrix.cpp, line 246
这个错误信息看起来像是 OpenCV 内部的问题,但实际上它几乎总是由你自己的代码传递了无效的尺寸参数导致的。更换 ONNX 模型后触发该错误------详细分析问题根源、调试方法以及解决方案。
1. 问题重现
基于 OpenCV DNN 模块加载 ONNX 模型进行目标检测的程序,原本使用 best0528.onnx 运行正常。当你将模型替换为 best0531.onnx 后,程序在推理后处理阶段突然崩溃,抛出上述 setSize 断言失败。
关键现象:
- 新旧模型的输入尺寸完全一致 :
[1, 3, 320, 320] - 新旧模型的输出名称和形状也相同 :
cat_18:[1,65,40,40]cat_19:[1,65,20,20]cat_20:[1,65,10,10]cat_29:[1,17,3,2100]
- 模型推理本身没有报错,但后处理时崩溃
2. 原因分析
cv::setSize 是 OpenCV 中用于调整矩阵(Mat)大小的内部函数。断言 s >= 0 失败意味着代码尝试创建一个尺寸(宽度或高度)为负数或零的矩阵。
在深度学习推理的后处理中,最常见的触发场景是:
- 从模型输出解码目标框的坐标 (x1, y1, x2, y2) 时 ,由于计算错误导致
x2 <= x1或y2 <= y1 - 随后使用这些坐标提取 ROI (例如
frame[y1:y2, x1:x2])或调用cv::resize时,因为宽度或高度为非正数而触发断言
具体到本例,经过调试发现:新模型输出的原始值中,边界框的宽度(w)或高度(h)出现了负数。当后处理代码使用 x1 = x - w/2、x2 = x + w/2 转换时,负的 w 导致 x2 < x1,最终使 y2 < y1 的无效框传递给了 OpenCV 函数。
为什么更换模型后会出现负数宽高?可能原因:
- 模型输出含义改变:旧模型可能输出经过 Sigmoid 限制在 0,1 的值,而新模型直接输出未经缩放的原始坐标(可能为负)
- 缺少解码步骤 :某些模型(如基于 Anchor 的检测器)需要对输出应用
exp()等变换才能得到正数宽高 - 训练或导出问题:新模型的边界框回归分支未约束宽高为正,导致推理时出现负值
3. 调试过程
第一步:确认输入输出结构一致
使用 ONNX Runtime 打印模型信息,排除结构不匹配:
python
import onnxruntime as ort
for model_path in ["best0528.onnx", "best0531.onnx"]:
sess = ort.InferenceSession(model_path)
print(f"--- {model_path} ---")
for inp in sess.get_inputs():
print(f"输入: {inp.name}, 形状: {inp.shape}")
for out in sess.get_outputs():
print(f"输出: {out.name}, 形状: {out.shape}")
结果:两个模型输入输出完全相同,排除结构问题。
第二步:检查输出数值范围
使用 ONNX Runtime 推理相同输入,打印每个输出层的最小值和最大值:
python
sess = ort.InferenceSession("best0531.onnx")
input_name = sess.get_inputs()[0].name
dummy_input = np.random.randn(1, 3, 320, 320).astype(np.float32)
outputs = sess.run(None, {input_name: dummy_input})
for i, o in enumerate(outputs):
print(f"Output {i}: shape={o.shape}, min={o.min():.3f}, max={o.max():.3f}")
输出示例:
text
# 旧模型 best0528
cat_29: min=-104.925, max=409.481
# 新模型 best0531
cat_29: min=-62.510, max=410.704
结论:数值范围相似,但新模型的最小值为 -62.51,说明输出中确实存在负数,这很可能是宽高为负的来源。
第三步:定位触发崩溃的具体代码行
在 OpenCV 后处理代码周围添加异常捕获和打印:
python
try:
# 模型推理
net.setInput(blob)
outs = net.forward()
# 后处理:解析输出 -> 计算 x1,y1,x2,y2
for detection in ...:
x1 = int(...); y1 = int(...); x2 = int(...); y2 = int(...)
# 关键检查
if x2 <= x1 or y2 <= y1:
print(f"无效框: ({x1},{y1}) -> ({x2},{y2})")
continue
roi = frame[y1:y2, x1:x2] # 如果跳过检查,这里会崩
cv2.resize(roi, (64, 64))
except cv2.error as e:
print("OpenCV 错误:", e)
import traceback; traceback.print_exc()
通过调用栈,发现崩溃发生在 roi = frame[y1:y2, x1:x2] 这一行,原因是 y2 < y1。
第四步:追踪到原始模型输出
打印从 cat_29 解码出的原始 (x, y, w, h) 值:
python
# 假设 cat_29 形状为 [1,17,3,2100],其中某几个通道是 x,y,w,h
for i in range(5):
x = cat_29[0, 10, 0, i] # 示例索引,需根据实际模型调整
y = cat_29[0, 11, 0, i]
w = cat_29[0, 12, 0, i]
h = cat_29[0, 13, 0, i]
print(f"Box {i}: x={x:.3f}, y={y:.3f}, w={w:.3f}, h={h:.3f}")
发现部分框的 w 或 h 为负数(例如 w=-0.23),证实了根本原因。
4. 解决方案
根据问题根源,有以下几种解决方案(按推荐程度排序):
方案一:在后处理中对宽高取绝对值(快速修复)
如果模型输出的宽高本应为正数,但出现了负值(可能由于训练或导出异常),可以直接取绝对值:
python
w = abs(w)
h = abs(h)
x1 = x - w/2
y1 = y - h/2
x2 = x + w/2
y2 = y + h/2
优点 :简单、立即生效
缺点:可能不是模型设计的本意,但通常不会对检测效果产生致命影响
方案二:应用正确的解码变换(根本解决)
查阅新模型的文档或导出脚本,确认是否需要对输出应用特殊变换(例如指数映射、Sigmoid、乘以 Anchor 尺寸等)。常见做法:
python
# 例如 YOLO 风格的解码
w = torch.exp(w) * anchor_w
h = torch.exp(h) * anchor_h
如果无法获取文档,可以用旧模型作为参考:对同一输入分别用新旧模型推理,比较 w 和 h 的比例关系,推测变换公式。
方案三:使用 ONNX Runtime 替代 OpenCV DNN
OpenCV DNN 在某些算子的实现上存在限制,而 ONNX Runtime 更加稳定和规范。如果模型本身没有问题,仅仅是因为 OpenCV 解析导致数值异常,切换到 ONNX Runtime 可以绕过问题:
python
import onnxruntime as ort
sess = ort.InferenceSession("best0531.onnx")
outputs = sess.run(None, {input_name: blob})
# 后续使用 outputs 进行后处理
优点 :避免 OpenCV DNN 的潜在 bug,支持更广泛的算子
缺点:需要额外安装库(onnxruntime 通常已安装)
方案四:添加防御性坐标修正
无论原因如何,在将坐标传递给 OpenCV 函数之前,确保 x1 <= x2 和 y1 <= y2:
python
x1, x2 = min(x1, x2), max(x1, x2)
y1, y2 = min(y1, y2), max(y1, y2)
# 同时确保坐标在图像边界内
x1 = max(0, x1); y1 = max(0, y1)
x2 = min(img_w, x2); y2 = min(img_h, y2)
优点 :防御性强,避免崩溃
缺点:可能产生宽度为 0 的框(仍需跳过)
5. 总结
Assertion failed (s >= 0) in cv::setSize 错误的核心原因是传入了无效的矩阵尺寸。在更换 ONNX 模型后出现该错误,通常是因为新模型的输出数值分布发生了变化,导致后处理计算出的坐标反转或宽高为负。
通过系统性的调试------先确认模型结构,再检查输出数值范围,然后定位崩溃代码行,最后追溯到原始模型输出------可以快速找到根本原因。解决方案根据情况选择:快速修复用取绝对值,根本解决用正确解码,长期推荐切换到 ONNX Runtime。