OpencvSharp 算子学习教案之 - Cv2.Subtract 重载2
重载2:Subtract(InputArray src1, Scalar 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:131
1. 函数名称(带参数签名)
csharp
public static void Subtract(
InputArray src1,
Scalar src2,
OutputArray dst,
InputArray? mask = null,
int dtype = -1)
2. 函数用途
这是 Subtract 的"数组减标量"重载,用来把一个常量从整张矩阵里逐元素减掉。
常见用途包括:
- 亮度偏置修正。
- 统一减去背景常数。
- 做阈值偏移或基准线扣除。
- 配合
mask只让局部区域发生变化。
3. 函数公式
当 src2 是标量时,可以理解为把这个标量扩展成与输入同形状的常量数组,然后再执行逐元素相减:
dst(I)=saturate(src1(I)−src2) dst(I) = \operatorname{saturate}(src1(I) - src2) dst(I)=saturate(src1(I)−src2)
如果提供 mask,只有 mask(I) \ne 0 的位置才会写入:
dst(I)←{saturate(src1(I)−src2),mask(I)≠0dst(I),mask(I)=0 dst(I) \leftarrow \begin{cases} \operatorname{saturate}(src1(I) - src2), & mask(I) \ne 0 \\ dst(I), & mask(I) = 0 \end{cases} dst(I)←{saturate(src1(I)−src2),dst(I),mask(I)=0mask(I)=0
对于多通道数组,Scalar 会按通道参与运算;如果只有一个通道,则只使用第一个标量分量。
4. 函数原理说明
OpenCV 对这个重载的处理可以概括为:
- 把标量转换成可参与运算的内部表示。
- 对每个元素做减法。
- 根据
dtype进行类型转换或饱和裁剪。 - 如果有
mask,只在命中的位置写入dst。
对初学者来说,这个重载最有价值的地方是:
- 你不需要显式创建一张"全是常数"的矩阵。
- 在做偏移修正时,它比先构造常量矩阵更直接。
- 如果输出类型选择得当,负数可以保留下来,便于观察真实差值。
5. 参数含义解析
| 参数名 | 类型 | 必填 | 含义 |
|---|---|---|---|
| src1 | InputArray | 是 | 左操作数矩阵 |
| src2 | Scalar | 是 | 被减去的标量常数 |
| dst | OutputArray | 是 | 输出结果 |
| mask | InputArray? | 否 | 8 位单通道掩码,只允许非零位置写入 dst |
| dtype | int | 否 | 输出深度,默认 -1 表示尽量沿用输入深度 |
6. 应用场景列表
| 场景名 | 场景说明 | 典型用途 |
|---|---|---|
| 场景A:偏置扣除 | 从每个像素中减去固定偏移量 | 亮度校正、零点修正 |
| 场景B:局部偏移修正 | 只在 mask 命中的区域做减法 | ROI 亮度校准 |
| 场景C:保留负值 | 输出设为 signed 或 float,观察真实差值 | 残差、误差分析 |
| 场景D:标量基准线 | 把一条常量基准线减到图像里 | 基线校正、模板比较 |
7. 函数使用示例(与 WPF 场景一一对应)
说明:下面代码与 WPF 控件中的场景B对应。这个场景专门演示数组减标量,并把结果写到
CV_16S,这样负值也能保留下来。
csharp
using OpenCvSharp;
// 输入矩阵,使用 8 位无符号整型保存。
var src1Data = new byte[,]
{
{ 10, 60, 100 },
{ 5, 200, 15 },
};
// 预先准备一个有明显数字的 dst,便于观察 mask 未命中的位置是否保留原值。
var seedData = new short[,]
{
{ 999, 999, 999 },
{ 999, 999, 999 },
};
var maskData = new byte[,]
{
{ 255, 0, 255 },
{ 0, 255, 0 },
};
using var src1 = Mat.FromPixelData(2, 3, MatType.CV_8UC1, src1Data);
using var mask = Mat.FromPixelData(2, 3, MatType.CV_8UC1, maskData);
using var dst = Mat.FromPixelData(2, 3, MatType.CV_16SC1, seedData);
// 这里减去常数 40,并把输出深度显式设为 CV_16S,便于保留负值。
Cv2.Subtract(src1, new Scalar(40), dst, mask, dtype: MatType.CV_16S);
// 例如 10 - 40 = -30,5 - 40 = -35,这些负值在 16S 中会被保留下来。
8. 函数使用注意事项
Scalar会按标量常数参与运算,不需要你手工扩展成矩阵。- 如果输出是无符号类型,负值会被裁剪到 0。
- 如果你要保留负值,优先考虑
CV_16S或CV_32F。 mask只控制写入,不改变减法本身。- 当
src1深度与目标输出深度差异较大时,显式设置dtype更安全。
9. 参数调优建议
- 亮度偏移类任务通常可以先试
CV_16S,看差值是否有负数。 - 如果后续还要做浮点运算,直接输出
CV_32F更省一步转换。 - 用
mask做局部修正时,先准备一个容易看出变化的dst初值。 - 若只是想看图像显示效果,
CV_8U也可以,但要接受负值裁剪为 0。
10. 示例代码运行说明(按场景关键逻辑)
- 准备一个
CV_8UC1输入矩阵。 - 准备一个 8 位单通道
mask,只让部分位置写入。 - 把
dst预填成 999,方便观察未命中的像素是否保留原值。 - 调用
Cv2.Subtract(src1, new Scalar(40), dst, mask, dtype: MatType.CV_16S)。 - 检查负值是否保留,以及 mask 没命中的位置是否保持 999。
11. 常见错误排查
- 错误:以为标量会自动变成一张常量矩阵
- 排查:
Scalar本身就会在内部参与运算,不需要自己扩展成矩阵。
- 排查:
- 错误:希望负值在
CV_8U里保留- 排查:改用
CV_16S或CV_32F。
- 排查:改用
- 错误:
mask不是 8 位单通道- 排查:使用
CV_8UC1掩码。
- 排查:使用
- 错误:忘记显式指定输出深度
- 排查:尤其在输入和输出类型不一致时,请写明
dtype。
- 排查:尤其在输入和输出类型不一致时,请写明
- 错误:把
src2看成矩阵而不是常量- 排查:这是"数组减标量",常量是对每个元素重复应用的。
对应 WPF 演示控件与样例代码:
- Features/Cv2Subtract/Cv2SubtractControl.xaml
- Samples/Cv2Subtract/SubtractArrayScalarSample.cs