OpencvSharp 算子学习教案之 - Cv2.Subtract 重载2

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 的"数组减标量"重载,用来把一个常量从整张矩阵里逐元素减掉。

常见用途包括:

  1. 亮度偏置修正。
  2. 统一减去背景常数。
  3. 做阈值偏移或基准线扣除。
  4. 配合 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 对这个重载的处理可以概括为:

  1. 把标量转换成可参与运算的内部表示。
  2. 对每个元素做减法。
  3. 根据 dtype 进行类型转换或饱和裁剪。
  4. 如果有 mask,只在命中的位置写入 dst。

对初学者来说,这个重载最有价值的地方是:

  1. 你不需要显式创建一张"全是常数"的矩阵。
  2. 在做偏移修正时,它比先构造常量矩阵更直接。
  3. 如果输出类型选择得当,负数可以保留下来,便于观察真实差值。

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. 函数使用注意事项

  1. Scalar 会按标量常数参与运算,不需要你手工扩展成矩阵。
  2. 如果输出是无符号类型,负值会被裁剪到 0。
  3. 如果你要保留负值,优先考虑 CV_16S 或 CV_32F。
  4. mask 只控制写入,不改变减法本身。
  5. 当 src1 深度与目标输出深度差异较大时,显式设置 dtype 更安全。

9. 参数调优建议

  1. 亮度偏移类任务通常可以先试 CV_16S,看差值是否有负数。
  2. 如果后续还要做浮点运算,直接输出 CV_32F 更省一步转换。
  3. 用 mask 做局部修正时,先准备一个容易看出变化的 dst 初值。
  4. 若只是想看图像显示效果,CV_8U 也可以,但要接受负值裁剪为 0。

10. 示例代码运行说明(按场景关键逻辑)

  1. 准备一个 CV_8UC1 输入矩阵。
  2. 准备一个 8 位单通道 mask,只让部分位置写入。
  3. 把 dst 预填成 999,方便观察未命中的像素是否保留原值。
  4. 调用 Cv2.Subtract(src1, new Scalar(40), dst, mask, dtype: MatType.CV_16S)。
  5. 检查负值是否保留,以及 mask 没命中的位置是否保持 999。

11. 常见错误排查

  1. 错误:以为标量会自动变成一张常量矩阵
    • 排查:Scalar 本身就会在内部参与运算,不需要自己扩展成矩阵。
  2. 错误:希望负值在 CV_8U 里保留
    • 排查:改用 CV_16S 或 CV_32F。
  3. 错误:mask 不是 8 位单通道
    • 排查:使用 CV_8UC1 掩码。
  4. 错误:忘记显式指定输出深度
    • 排查:尤其在输入和输出类型不一致时,请写明 dtype。
  5. 错误:把 src2 看成矩阵而不是常量
    • 排查:这是"数组减标量",常量是对每个元素重复应用的。

对应 WPF 演示控件与样例代码:

  • Features/Cv2Subtract/Cv2SubtractControl.xaml
  • Samples/Cv2Subtract/SubtractArrayScalarSample.cs
相关推荐
传奇开心果编程10 分钟前
【Flutter入门练中学】第8课:动画与过渡
android·学习·flutter·ui·ios
Chen—LSN43 分钟前
C语言——文件操作(2)
c语言·开发语言·c++·经验分享·笔记·算法·c#
程序员yu1 小时前
会编网络:别被入门教程骗了,Python实用能力不在语法本身
开发语言·python·学习·算法
Lintongzg1 小时前
千卡训练的隐形账单:通信不是瓶颈
笔记·学习·性能优化·llm
dadaobusi1 小时前
学习: SIOV
学习
LuminousCPP2 小时前
从零开始学 C++(三):类和对象入门|从 struct 到 class,理解封装、对象与 this 指针
c++·笔记·学习·类和对象
传奇开心果编程2 小时前
【Flutter入门练中学】第11课:状态管理进阶与声明式路由
android·学习·flutter·ui·ios
承渊政道2 小时前
Linux系统学习【进程信号详细解析——认识、产生、保存以及捕捉信号】
linux·学习·ubuntu·ssh·vs code·进程信号
个 人 练 习 生3 小时前
C++中的内存管理
开发语言·c++·经验分享·学习
淡海水4 小时前
00-Unity和C#的GC剖析与对比-全篇导览
unity·c#·游戏引擎·gc