OpencvSharp 算子学习教案之 - Cv2.Multiply
大家好,Opencv在很多工程项目中都会用到,而OpencvSharp则是以C#开发与实现的Opencv操作库,对.NET开发人员友好,但很多API的中文资料、应用场景及常见坑点等缺乏系统性归纳,因此这系列博客将给大家带来Cv2及Mat对象全系列算子学习教案,供大家参考学习。
Cv2.Multiply
- 教案版本:V1.0
- 面向对象:OpenCvSharp 初学者
- 所属模块:core
- 源码位置:OpenCvSharp/Cv2/Cv2_core.cs:185
1. 函数名称(带参数签名)
csharp
public static void Multiply(
InputArray src1,
InputArray src2,
OutputArray dst,
double scale = 1,
int dtype = -1)
2. 函数用途
Cv2.Multiply 用来做逐元素乘法。它不是矩阵乘法,而是把两个输入矩阵中对应位置的元素一一相乘,再乘上 scale 系数。
这个函数常见于:
- 像素强度放大或缩小。
- 归一化前后的数值变换。
- 混合深度输入时,把结果提升到更安全的输出类型。
- 观察
CV_32S输出时"不会像 8U 那样饱和裁剪"的特殊行为。
3. 函数公式
对每个元素 III,OpenCV 的基本行为可以写成:
dst(I)=saturate(scale⋅src1(I)⋅src2(I)) dst(I) = \operatorname{saturate}(scale \cdot src1(I) \cdot src2(I)) dst(I)=saturate(scale⋅src1(I)⋅src2(I))
其中 saturate 表示按输出深度做类型转换与裁剪。
3.1 输出深度的提示
- 当两个输入深度相同,而且你不想改变输出类型时,
dtype可以保持默认的-1。 - 当输入深度不同,或者你希望把结果直接放到更安全的类型里时,应该显式指定
dtype。 CV_32S是一个特殊输出类型,OpenCV 的实现和测试都把它当作"不会做常规饱和裁剪"的分支来处理。
4. 函数原理说明
Multiply 的本质是逐元素计算,不是线性代数里的矩阵乘法。
对初学者来说,最重要的理解点有四个:
src1和src2是位置一一对应地相乘。scale是额外的比例系数,会同时影响所有元素。dtype决定最终结果放进什么类型里。- 如果你输出到
CV_8U,大于 255 的结果会被压到 255;如果输出到CV_32S,这一点通常不会发生。
OpenCV 内部对这个函数会根据输入深度和 dtype 选择不同的处理路径,因此它既能处理普通同深度输入,也能在你明确指定输出类型后处理混合深度数据。
5. 参数含义解析
| 参数名 | 类型 | 必填 | 含义 |
|---|---|---|---|
| src1 | InputArray | 是 | 第一个输入数组 |
| src2 | InputArray | 是 | 第二个输入数组 |
| dst | OutputArray | 是 | 输出结果 |
| scale | double | 否 | 额外缩放系数 |
| dtype | int | 否 | 输出深度,默认 -1 表示尽量沿用输入深度 |
6. 应用场景列表
| 场景名 | 场景说明 | 典型用途 |
|---|---|---|
| 场景A:8U 饱和乘法 | 两个 8 位矩阵相乘,观察结果如何被压到 255 | 像素叠乘、亮度增强 |
| 场景B:scale 缩放因子 | 用 scale = 0.5 观察乘积如何整体缩小 |
归一化、强度缩放 |
| 场景C:混合深度与 dtype 提升 | 8U 和 16U 输入显式提升到 CV_32F |
混合精度处理 |
| 场景D:CV_32S 特殊行为 | 用 32 位有符号输出保留更大的整数乘积 | 大数值分析、特殊类型验证 |
7. 函数使用示例(与 WPF 场景一一对应)
说明:下面四段代码与 WPF 控件中的四个场景一一对应。示例尽量保持初学者友好,重点展示
scale、dtype和输出深度的影响。
7.1 场景A:8U 饱和乘法
csharp
using OpenCvSharp;
// 两张 8 位无符号矩阵,元素相乘后很容易超过 255。
var src1Data = new byte[,]
{
{ 20, 15 },
{ 12, 30 },
};
var src2Data = new byte[,]
{
{ 20, 18 },
{ 25, 12 },
};
using var src1 = Mat.FromPixelData(2, 2, MatType.CV_8UC1, src1Data);
using var src2 = Mat.FromPixelData(2, 2, MatType.CV_8UC1, src2Data);
using var dst = new Mat();
// 20 * 20 = 400,12 * 25 = 300,这些值都会被压到 255。
Cv2.Multiply(src1, src2, dst, dtype: MatType.CV_8U);
7.2 场景B:scale 缩放因子
csharp
using OpenCvSharp;
// 这里使用会产生半整数结果的数据,方便看出 scale 的作用。
var src1Data = new byte[,]
{
{ 3, 5 },
{ 7, 9 },
};
var src2Data = new byte[,]
{
{ 5, 7 },
{ 11, 13 },
};
using var src1 = Mat.FromPixelData(2, 2, MatType.CV_8UC1, src1Data);
using var src2 = Mat.FromPixelData(2, 2, MatType.CV_8UC1, src2Data);
using var dst = new Mat();
// 先做逐元素乘法,再整体乘以 0.5。
Cv2.Multiply(src1, src2, dst, scale: 0.5, dtype: MatType.CV_32F);
// 3 * 5 * 0.5 = 7.5
// 5 * 7 * 0.5 = 17.5
7.3 场景C:混合深度与 dtype 提升
csharp
using OpenCvSharp;
// 一个 8 位无符号矩阵和一个 16 位无符号矩阵。
var src1Data = new byte[,]
{
{ 7, 9 },
{ 11, 13 },
};
var src2Data = new ushort[,]
{
{ 100, 200 },
{ 300, 400 },
};
using var src1 = Mat.FromPixelData(2, 2, MatType.CV_8UC1, src1Data);
using var src2 = Mat.FromPixelData(2, 2, MatType.CV_16UC1, src2Data);
using var dst = new Mat();
// 混合深度时,显式把输出提升到 CV_32F 更稳妥。
Cv2.Multiply(src1, src2, dst, scale: 0.25, dtype: MatType.CV_32F);
// 7 * 100 * 0.25 = 175.0
// 13 * 400 * 0.25 = 1300.0
7.4 场景D:CV_32S 特殊行为
csharp
using OpenCvSharp;
// 32 位有符号整数输入,用来演示更大的乘积不会像 8U 那样被压到 255。
var src1Data = new int[,]
{
{ 30000, 40000 },
{ 50000, 60000 },
};
var src2Data = new int[,]
{
{ 2, 3 },
{ 4, 5 },
};
using var src1 = Mat.FromPixelData(2, 2, MatType.CV_32SC1, src1Data);
using var src2 = Mat.FromPixelData(2, 2, MatType.CV_32SC1, src2Data);
using var dst = new Mat();
// 输出仍然保持 32S,这样 60000、120000 这样的值可以完整保留。
Cv2.Multiply(src1, src2, dst, dtype: MatType.CV_32S);
// 30000 * 2 = 60000
// 60000 * 5 = 300000
8. 函数使用注意事项
Multiply是逐元素乘法,不是矩阵乘法。scale会参与最终结果,做归一化或缩放时非常有用。src1和src2必须同尺寸、同通道数。- 当输入深度不同的时候,请显式指定
dtype,不要只依赖默认值。 - 如果输出是
CV_8U,大结果会被饱和压到 255;如果输出是CV_32S,常规饱和裁剪通常不会发生。
9. 参数调优建议
- 想观察饱和行为时,优先使用
CV_8U输出。 - 想看真实的缩放结果时,优先使用
CV_32F输出。 - 混合深度演示时,把
dtype显式写成CV_32F,这样最容易看懂。 - 如果结果里出现大量 255,不一定是乘法错了,更多时候是输出类型太窄。
- 如果你要验证
CV_32S的特殊行为,建议使用仍在 int32 范围内但明显大于 255 的数值。
10. 示例代码运行说明(按场景关键逻辑)
场景A(8U 饱和乘法)
- 准备两个
CV_8UC1矩阵。 - 调用
Cv2.Multiply(src1, src2, dst, dtype: MatType.CV_8U)。 - 观察乘积是否被压到 255。
场景B(scale 缩放因子)
- 准备两个
CV_8UC1矩阵。 - 把
scale设为0.5。 - 输出用
CV_32F,便于观察半整数结果。
场景C(混合深度与 dtype 提升)
- 准备一个
CV_8UC1输入和一个CV_16UC1输入。 - 显式把输出设为
CV_32F。 - 观察混合深度数据如何被安全地提升到浮点结果。
场景D(CV_32S 特殊行为)
- 准备两个
CV_32SC1输入。 - 输出也设为
CV_32S。 - 观察大于 255 的整数乘积能否被完整保留。
11. 常见错误排查
- 错误:把
Multiply当成矩阵乘法- 排查:
Multiply是逐元素乘法,不是GEMM。
- 排查:
- 错误:忘记
scale会直接影响输出- 排查:检查调用里是否写了
scale,默认值是1。
- 排查:检查调用里是否写了
- 错误:输出仍然是
CV_8U,结果被压成 255- 排查:把
dtype改成CV_32F或CV_32S再看。
- 排查:把
- 错误:混合深度时不指定
dtype- 排查:显式指定输出深度,避免 OpenCV 选择不符合预期的类型。
- 错误:以为
CV_32S也会像CV_8U一样饱和裁剪- 排查:
CV_32S是特殊路径,应该单独验证。
- 排查:
对应 WPF 演示控件与样例代码:
- Features/Cv2Multiply/Cv2MultiplyControl.xaml
- Samples/Cv2Multiply/MultiplySaturationSample.cs
- Samples/Cv2Multiply/MultiplyScaleSample.cs
- Samples/Cv2Multiply/MultiplyMixedDepthPromotionSample.cs
- Samples/Cv2Multiply/MultiplyInt32SpecialSample.cs