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

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

这个重载在下面这些场景里很实用:

  1. 图像反相或近似反相。
  2. 以固定参考值为基准计算偏差。
  3. 常量背景扣除。
  4. 生成"离某个阈值还差多少"的结果图。

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_16SCV_32F 等类型时,负值可以被保留下来。

4. 函数原理说明

这个重载的运算方向和数组减数组不同:

  1. 先把标量 src1 视为一个常量基准。
  2. 再让矩阵 src2 的每个元素从这个基准里减去。
  3. dtype 决定最终输出是继续保持无符号、保留符号,还是切换到浮点。
  4. mask 依然只影响写入位置。

对初学者来说,这个方向最容易写反。记住一个简单口诀:

  1. Subtract(src1, src2, ...) 永远是左边减右边。
  2. 这里左边是 Scalar,右边是矩阵。
  3. 所以结果是"常量减图像",不是"图像减常量"。

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

  1. 这个重载的方向是"标量减数组",不要和"数组减标量"混淆。
  2. 若输出是 CV_8U,负值会被裁剪为 0。
  3. 若要观察真实差值,优先使用 CV_16SCV_32F
  4. mask 只控制写回位置,不改变标量减法本身。
  5. 多通道场景下,标量会按通道参与运算。

9. 参数调优建议

  1. 需要做反相图像时,先试 Scalar(255) 减 8 位图像。
  2. 需要看真实偏差时,建议使用 CV_32F 输出。
  3. 如果想直观看出 mask 作用,先把 dst 预填为一个容易识别的值。
  4. 当结果里出现负数时,不要先怀疑公式,先检查输出深度是不是无符号类型。

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

  1. 准备一个 2x3 的 CV_8UC1 输入矩阵。
  2. 准备一个 2x3 的 mask,只让部分位置写入。
  3. dst 预填成 999f,便于观察未命中的位置。
  4. 调用 Cv2.Subtract(new Scalar(100), src2, dst, mask, dtype: MatType.CV_32F)
  5. 检查 100 - 130 = -30 这样的负值是否被完整保留。

11. 常见错误排查

  1. 错误:把这个重载写成数组减常量
    • 排查:确认函数签名里左边是 Scalar,右边才是 InputArray
  2. 错误:期待 CV_8U 输出保留负值
    • 排查:改成 CV_16SCV_32F
  3. 错误:mask 不是 8 位单通道
    • 排查:使用 CV_8UC1 掩码。
  4. 错误:以为标量和数组顺序可以互换
    • 排查:Subtract 的顺序是严格的,左减右。
  5. 错误:看到全 0 就以为计算失败
    • 排查:如果输出是无符号类型,负值本来就会被裁剪成 0。

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

  • Features/Cv2Subtract/Cv2SubtractControl.xaml
  • Samples/Cv2Subtract/SubtractScalarArraySample.cs
相关推荐
HERR_QQ2 小时前
端到端课程自用 1课 感知部分
笔记·学习·自动驾驶
辞旧 lekkk2 小时前
【Git】远程操作与标签管理
linux·git·学习·萌新
jrlong2 小时前
HelloAgents 进阶篇 task00,task01
笔记·学习
fengci.2 小时前
php反序列化(复习)(第五章)
android·开发语言·学习·php
ICscholar3 小时前
MoE负载均衡损失 & 梯度累加除法
人工智能·学习·算法
嵌入式小企鹅3 小时前
国产AI全栈迁移、涨价潮蔓延、RISC-V测评工具发布
人工智能·学习·开源·嵌入式·边缘计算·半导体·昇腾
殇淋狱陌3 小时前
【初始Python】Python学习基础(数据类型、定义、变量、下标、目前的开发语言对比)
开发语言·python·学习
xiaoxiaoxiaolll3 小时前
数据驱动下的人工电磁材料逆向设计与智能优化研究
人工智能·学习
傻啦嘿哟3 小时前
如何用 Python 拆分 Word 文件:高效分割大型文档的完整指南
开发语言·c#