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

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

重载1:Subtract(InputArray 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:100

1. 函数名称(带参数签名)

csharp 复制代码
public static void Subtract(
    InputArray src1,
    InputArray src2,
    OutputArray dst,
    InputArray? mask = null,
    int dtype = -1)

2. 函数用途

这是 Subtract 的数组-数组重载,负责对两个同尺寸、同通道数的输入做逐元素相减。

这个重载最常见的用途有:

  1. 计算两张图像的差异图。
  2. 做运动检测、背景差分和局部变化提取。
  3. 配合 mask 只更新目标区域。
  4. 配合 dtype 保留负数、扩大动态范围,或者显式控制输出深度。

3. 函数公式

没有 mask 时,OpenCV 的逐元素行为可以写成:

dst(I)=saturate⁡(src1(I)−src2(I)) dst(I) = \operatorname{saturate}(src1(I) - src2(I)) dst(I)=saturate(src1(I)−src2(I))

当提供 mask 时,只有 mask(I) \ne 0 的位置才会写入:

dst(I)←{saturate⁡(src1(I)−src2(I)),mask(I)≠0dst(I),mask(I)=0 dst(I) \leftarrow \begin{cases} \operatorname{saturate}(src1(I) - src2(I)), & mask(I) \ne 0 \\ dst(I), & mask(I) = 0 \end{cases} dst(I)←{saturate(src1(I)−src2(I)),dst(I),mask(I)=0mask(I)=0

这里的 saturate 表示按输出深度做饱和裁剪。对于 CV_8U,负数会被裁剪成 0。

3.1 输出深度的特殊规则

如果输出深度是 CV_32S,OpenCV 不会做饱和裁剪。其余整数输出通常都会做饱和处理,因此它和 CV_8UCV_16S 的结果语义并不完全相同。

4. 函数原理说明

OpenCV 内部会先检查输入是否兼容,再把结果写到 dst

  1. src1src2 必须同尺寸、同通道数。
  2. 如果输入深度不同,而你又没有显式给出 dtype,OpenCV 会报错,要求你明确输出类型。
  3. mask 只控制写入位置,不改变运算本身。
  4. 当输出深度是整数类型时,OpenCV 会按目标类型做裁剪或保留符号。

对初学者来说,最需要记住的是:

  1. Subtract 的数学意义是"左边减右边",不是绝对值差。
  2. dtype 决定最终结果装进什么类型里,而不是输入怎么减。
  3. mask 不是"参与减法的第三个数组",而是"控制哪些位置允许写回 dst"。

5. 参数含义解析

参数名 类型 必填 含义
src1 InputArray 左操作数
src2 InputArray 右操作数
dst OutputArray 输出结果
mask InputArray? 8 位单通道掩码,只允许非零位置写入 dst
dtype int 输出深度,默认 -1 表示尽量沿用输入深度

6. 应用场景列表

场景名 场景说明 典型用途
场景A:图像差分 两张图像做逐元素相减,输出差异图 变化检测、背景建模
场景B:局部更新 配合 mask 只更新部分像素 ROI 处理、分段比较
场景C:保留负值 CV_16SCV_32F 保留负差值 残差分析、视觉误差图
场景D:混合深度处理 输入类型不同但显式指定输出深度 数据归一化、类型转换演示

7. 函数使用示例(与 WPF 场景一一对应)

说明:下面代码与 WPF 控件中的场景A对应。这个场景专门演示逐元素相减、mask 选择性写入,以及 CV_8U 输出下的饱和裁剪。

csharp 复制代码
using OpenCvSharp;

// 两张 8 位无符号矩阵,尺寸完全一致。
var src1Data = new byte[,]
{
    { 15, 80, 200 },
    { 0, 30, 100 },
    { 255, 128, 10 },
};

var src2Data = new byte[,]
{
    { 20, 50, 120 },
    { 1, 40, 110 },
    { 5, 200, 20 },
};

var maskData = new byte[,]
{
    { 255, 0, 255 },
    { 0, 255, 0 },
    { 255, 0, 255 },
};

var seedData = new byte[,]
{
    { 77, 77, 77 },
    { 77, 77, 77 },
    { 77, 77, 77 },
};

using var src1 = Mat.FromPixelData(3, 3, MatType.CV_8UC1, src1Data);
using var src2 = Mat.FromPixelData(3, 3, MatType.CV_8UC1, src2Data);
using var mask = Mat.FromPixelData(3, 3, MatType.CV_8UC1, maskData);
using var dst = Mat.FromPixelData(3, 3, MatType.CV_8UC1, seedData);

// 这里显式指定 CV_8U,便于观察负值如何被裁剪到 0。
Cv2.Subtract(src1, src2, dst, mask, dtype: MatType.CV_8U);

// 当 src1 - src2 小于 0 时,输出会变成 0。

8. 函数使用注意事项

  1. src1src2 必须同尺寸、同通道数。
  2. mask 必须是 8 位单通道矩阵,并且尺寸要与输入一致。
  3. 如果输入深度不同,请显式指定 dtype,不要依赖默认值。
  4. 当输出是 CV_8U 等无符号类型时,负数会被裁剪到 0。
  5. 当输出是 CV_32S 时,OpenCV 不做饱和裁剪,这一点和其他整数输出不同。

9. 参数调优建议

  1. 需要看"真实差值"时,优先把输出设成 CV_16SCV_32F
  2. 需要做图像差分显示时,CV_8U 常用于快速查看亮暗区域变化。
  3. 先用小矩阵验证 mask 语义,再放大到图像尺寸,能更快排查问题。
  4. 如果你发现负值都变成了 0,通常是输出类型太窄,而不是减法算错了。

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

  1. 准备两张 3x3 的 CV_8UC1 矩阵。
  2. 准备一个 8 位单通道 mask,让部分位置允许写入。
  3. 先把 dst 预填成 77,便于看出未命中的位置是否保留原值。
  4. 调用 Cv2.Subtract(src1, src2, dst, mask, dtype: MatType.CV_8U)
  5. 观察负数是否被裁剪为 0,以及 mask 没命中的位置是否保持初始值。

11. 常见错误排查

  1. 错误:两个输入尺寸不一致
    • 排查:确认 src1src2 的行列数完全一致。
  2. 错误:把 Subtract 当成绝对值差
    • 排查:Subtract 保留方向,结果是 src1 - src2
  3. 错误:mask 不是 CV_8UC1
    • 排查:把掩码改成 8 位单通道,并确认尺寸一致。
  4. 错误:输入深度不同却不指定 dtype
    • 排查:显式指定输出深度,例如 CV_32FCV_16S
  5. 错误:期待负数能在 CV_8U 中保留
    • 排查:改用有符号或浮点输出。

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

  • Features/Cv2Subtract/Cv2SubtractControl.xaml
  • Samples/Cv2Subtract/SubtractArrayArraySample.cs
相关推荐
zhangrelay2 小时前
智能时代机器人工程师・云原生 + 大模型 + 智能体 全栈成长计划(2026 版)
笔记·学习
张人玉2 小时前
SMT 贴片机上位机项目
开发语言·c#
华阙之梦2 小时前
【GIS课堂】
学习
知识分享小能手2 小时前
MongoDB入门学习教程,从入门到精通,部署MongoDB(24)
数据库·学习·mongodb
asdzx672 小时前
C#:从 URL 下载 PDF 文档到本地
开发语言·pdf·c#
m0_716765232 小时前
数据结构--循环链表、双向链表的插入、删除、查找详解
开发语言·数据结构·c++·学习·链表·青少年编程·visual studio
南無忘码至尊3 小时前
Unity学习90天 - 第 5 天 - 阶段小项目
学习·unity·c#·游戏引擎
韩楚风3 小时前
PostgreSQL入门与进阶学习,体系化的SQL知识,完成终极目标高可用与容灾,性能优化与架构设计,以及安全策略
sql·学习·postgresql
亚空间仓鼠3 小时前
Python学习日志(四):实例
开发语言·python·学习