OpencvSharp 算子学习教案之 - Cv2.Subtract 重载3
重载3:Subtract(Scalar src1, InputArray src2, OutputArray dst, InputArray? mask = null, int dtype = -1)
大家好,Opencv在很多工程项目中都会用到,而OpencvSharp则是以C#开发与实现的Opencv操作库,对.NET开发人员友好,但很多API的中文资料、应用场景及常见坑点等缺乏系统性归纳,因此这系列博客将给大家带来Cv2及Mat对象全系列算子学习教案,供大家参考学习。
Cv2.Subtract
- 教案版本:V1.0
- 面向对象:OpenCvSharp 初学者
- 所属模块:core
- 源码位置:OpenCvSharp/Cv2/Cv2_core.cs:158
1. 函数名称(带参数签名)
csharp
public static void Subtract(
Scalar src1,
InputArray src2,
OutputArray dst,
InputArray? mask = null,
int dtype = -1)
2. 函数用途
这是 Subtract 的"标量减数组"重载,用来把矩阵从一个固定常量里减掉。
这个重载在下面这些场景里很实用:
- 图像反相或近似反相。
- 以固定参考值为基准计算偏差。
- 常量背景扣除。
- 生成"离某个阈值还差多少"的结果图。
3. 函数公式
标量减数组可以写成:
dst(I)=saturate(src1−src2(I)) dst(I) = \operatorname{saturate}(src1 - src2(I)) dst(I)=saturate(src1−src2(I))
如果提供 mask,只有 mask(I) \ne 0 的位置才会写入:
dst(I)←{saturate(src1−src2(I)),mask(I)≠0dst(I),mask(I)=0 dst(I) \leftarrow \begin{cases} \operatorname{saturate}(src1 - src2(I)), & mask(I) \ne 0 \\ dst(I), & mask(I) = 0 \end{cases} dst(I)←{saturate(src1−src2(I)),dst(I),mask(I)=0mask(I)=0
当输出深度较小时,负值会被裁剪;当输出深度选择 CV_16S、CV_32F 等类型时,负值可以被保留下来。
4. 函数原理说明
这个重载的运算方向和数组减数组不同:
- 先把标量
src1视为一个常量基准。 - 再让矩阵
src2的每个元素从这个基准里减去。 dtype决定最终输出是继续保持无符号、保留符号,还是切换到浮点。mask依然只影响写入位置。
对初学者来说,这个方向最容易写反。记住一个简单口诀:
Subtract(src1, src2, ...)永远是左边减右边。- 这里左边是
Scalar,右边是矩阵。 - 所以结果是"常量减图像",不是"图像减常量"。
5. 参数含义解析
| 参数名 | 类型 | 必填 | 含义 |
|---|---|---|---|
| src1 | Scalar | 是 | 左操作数常量 |
| src2 | InputArray | 是 | 右操作数矩阵 |
| dst | OutputArray | 是 | 输出结果 |
| mask | InputArray? | 否 | 8 位单通道掩码,只允许非零位置写入 dst |
| dtype | int | 否 | 输出深度,默认 -1 表示尽量沿用输入深度 |
6. 应用场景列表
| 场景名 | 场景说明 | 典型用途 |
|---|---|---|
| 场景A:反相参考值 | 用固定亮度常量减去图像 | 反相、对比度分析 |
| 场景B:偏差量化 | 观察每个像素距离常量基准还有多远 | 阈值比较、误差图 |
| 场景C:局部逆运算 | 配合 mask 只处理部分区域 | ROI 逆向校正 |
| 场景D:浮点输出保留负值 | 用 CV_32F 观察真实差值 |
数值分析、梯度场可视化 |
7. 函数使用示例(与 WPF 场景一一对应)
说明:下面代码与 WPF 控件中的场景C对应。这个场景使用常量 100 减去图像矩阵,并把输出设成
CV_32F,这样负值也能完整保留。
csharp
using OpenCvSharp;
// 右操作数矩阵:每个位置都从固定常量 100 里减去。
var src2Data = new byte[,]
{
{ 10, 25, 80 },
{ 100, 110, 130 },
};
// 预填 dst,方便观察 mask 没命中的位置是否保留原值。
var seedData = new float[,]
{
{ 999f, 999f, 999f },
{ 999f, 999f, 999f },
};
var maskData = new byte[,]
{
{ 255, 0, 255 },
{ 0, 255, 0 },
};
using var src2 = Mat.FromPixelData(2, 3, MatType.CV_8UC1, src2Data);
using var mask = Mat.FromPixelData(2, 3, MatType.CV_8UC1, maskData);
using var dst = Mat.FromPixelData(2, 3, MatType.CV_32FC1, seedData);
// 这里做的是 100 - src2,而不是 src2 - 100。
Cv2.Subtract(new Scalar(100), src2, dst, mask, dtype: MatType.CV_32F);
// 例如:100 - 130 = -30,这个负值在 float 输出里会被保留下来。
8. 函数使用注意事项
- 这个重载的方向是"标量减数组",不要和"数组减标量"混淆。
- 若输出是
CV_8U,负值会被裁剪为 0。 - 若要观察真实差值,优先使用
CV_16S或CV_32F。 mask只控制写回位置,不改变标量减法本身。- 多通道场景下,标量会按通道参与运算。
9. 参数调优建议
- 需要做反相图像时,先试
Scalar(255)减 8 位图像。 - 需要看真实偏差时,建议使用
CV_32F输出。 - 如果想直观看出 mask 作用,先把 dst 预填为一个容易识别的值。
- 当结果里出现负数时,不要先怀疑公式,先检查输出深度是不是无符号类型。
10. 示例代码运行说明(按场景关键逻辑)
- 准备一个 2x3 的
CV_8UC1输入矩阵。 - 准备一个 2x3 的
mask,只让部分位置写入。 - 把
dst预填成 999f,便于观察未命中的位置。 - 调用
Cv2.Subtract(new Scalar(100), src2, dst, mask, dtype: MatType.CV_32F)。 - 检查
100 - 130 = -30这样的负值是否被完整保留。
11. 常见错误排查
- 错误:把这个重载写成数组减常量
- 排查:确认函数签名里左边是
Scalar,右边才是InputArray。
- 排查:确认函数签名里左边是
- 错误:期待
CV_8U输出保留负值- 排查:改成
CV_16S或CV_32F。
- 排查:改成
- 错误:
mask不是 8 位单通道- 排查:使用
CV_8UC1掩码。
- 排查:使用
- 错误:以为标量和数组顺序可以互换
- 排查:
Subtract的顺序是严格的,左减右。
- 排查:
- 错误:看到全 0 就以为计算失败
- 排查:如果输出是无符号类型,负值本来就会被裁剪成 0。
对应 WPF 演示控件与样例代码:
- Features/Cv2Subtract/Cv2SubtractControl.xaml
- Samples/Cv2Subtract/SubtractScalarArraySample.cs