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_16SCV_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_16SCV_32F
  3. 错误:mask 不是 8 位单通道
    • 排查:使用 CV_8UC1 掩码。
  4. 错误:忘记显式指定输出深度
    • 排查:尤其在输入和输出类型不一致时,请写明 dtype
  5. 错误:把 src2 看成矩阵而不是常量
    • 排查:这是"数组减标量",常量是对每个元素重复应用的。

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

  • Features/Cv2Subtract/Cv2SubtractControl.xaml
  • Samples/Cv2Subtract/SubtractArrayScalarSample.cs
相关推荐
小陈phd2 小时前
多模态大模型学习笔记(三十五)——OCR全景认知:从字符识别到多模态理解的百年演进
笔记·学习·ocr
小饕2 小时前
RAG学习之 - 检索质量评估指标详解:从概念到代码实战
开发语言·人工智能·python·学习
TTGGGFF2 小时前
SnapTranslate 3.0 正式发布:全局划词翻译 + 完整英语学习闭环,一站式搞定查词、记词、复习
学习·英语学习·生词本
回忆2012初秋2 小时前
C# 射线算法:判断GPS点是否在车辆工作区域内
linux·算法·c#
_李小白2 小时前
【OSG学习笔记】Day 52: FadeText
笔记·学习
The Shio2 小时前
上位机对接设备协议踩坑指南
网络·单片机·嵌入式硬件·物联网·c#·.net
CompaqCV2 小时前
OpencvSharp 算子学习教案之 - Cv2.Add
学习·c#·opencvsharp算子
CompaqCV2 小时前
OpencvSharp 算子学习教案之 - Cv2.Subtract 重载3
学习·c#·opencvsharp算子·opencv教程
HERR_QQ3 小时前
端到端课程自用 1课 感知部分
笔记·学习·自动驾驶