OpenCV函数subtract()的原型如下:
void cv::subtract( InputArray src1,
InputArray src2,
OutputArray dst,
InputArray mask = noArray(),
int dtype = -1
)
官方文档说明如下:
看了以上文档并结合自己的使用,补充说明以下几点:
1 函数subtract()不仅能做相同大小矩阵之间的差值运算,还可做矩阵与标量之间的相减运算。注意,当矩阵是多通道时,相应的标量类似于一个向量。比如三通道的矩阵与标量做相减运算,那么相应的标量需要三个数,形式上应该是cv::Scalar(x1,x2,x3)
2 当做相同大小矩阵的差值运算时,可以直接用减号“-”代替函数调用形式,此时用减号“-”通过运算符重载调用函数subtract()。
即如果A和B是两个大小相同的矩阵,则
cv::subtract(image1, image2, image3);
等效于下面这条语句:
image3 = image1 - image2;
3 dst矩阵并不需要事先定义大小、类型。比如下面的代码是可以的:
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <iostream>
int main( )
{
cv::Mat image1( 3, 3, CV_8UC1, cv::Scalar(5) );
cv::Mat image2( 3, 3, CV_8UC1, cv::Scalar(10) );
cv::Mat image3;
cv::subtract(image1, image2, image3, cv::noArray(), CV_16S);
// 输出矩阵结果
std::cout <<"imag1的数据如下:\n"<<image1 << std::endl<< std::endl;
std::cout <<"imag2的数据如下:\n"<<image2 << std::endl<< std::endl;
std::cout <<"imag3的数据如下:\n"<<image3 << std::endl<< std::endl;
return 0;
}
4 如果运算结果中可能有负值并且要保留负值,有以下两种方法解决:
① 将两个相减的矩阵数据类型设为有符号型或用函数convertTo()转化为有符号型,不用去考虑目标矩阵的数据类型,假如目标矩阵原来的数据类型为CV_8U,则会自动进行转换为有符号型。比如下面的代码:
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <iostream>
int main( )
{
cv::Mat image1( 3, 3, CV_16S, cv::Scalar(5) );
cv::Mat image2( 3, 3, CV_16S, cv::Scalar(10) );
cv::Mat image3( 3, 3, CV_8UC1, cv::Scalar(0));
cv::subtract(image1, image2, image3, cv::noArray(), CV_16S);
// 输出矩阵结果
std::cout <<"imag1的数据如下:\n"<<image1 << std::endl<< std::endl;
std::cout <<"imag2的数据如下:\n"<<image2 << std::endl<< std::endl;
std::cout <<"imag3的数据如下:\n"<<image3 << std::endl<< std::endl;
return 0;
}
运行结果如下:
② 直接设置函数subtract()的第四个参数为有符号型,函数subtract()的第四个参数实际上是设置目标矩阵的数据类型,比如上面的代码便是采用的这种方法。示例代码如下:
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <iostream>
int main( )
{
cv::Mat image1( 3, 3, CV_8UC1, cv::Scalar(5) );
cv::Mat image2( 3, 3, CV_8UC1, cv::Scalar(10) );
cv::Mat image3;
cv::subtract(image1, image2, image3, cv::noArray(), CV_16S);
// 输出矩阵结果
std::cout <<"imag1的数据如下:\n"<<image1 << std::endl<< std::endl;
std::cout <<"imag2的数据如下:\n"<<image2 << std::endl<< std::endl;
std::cout <<"imag3的数据如下:\n"<<image3 << std::endl<< std::endl;
return 0;
}
用这种方法处理时,目标矩阵的数据类型也会被重置,比如下面的代码中我事先设置的img3的数据类型为CV_8U,但是经过函数subtract()的运算后,它的数据类型被改变成了CV_16S
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <iostream>
int main( )
{
cv::Mat image1( 3, 3, CV_8UC1, cv::Scalar(5) );
cv::Mat image2( 3, 3, CV_8UC1, cv::Scalar(10) );
cv::Mat image3( 3, 3, CV_8UC1, cv::Scalar(0));
std::cout <<"未经函数subtract()运算前imag3的数据类型为:\n"<<image3.depth() << std::endl<< std::endl;
cv::subtract(image1, image2, image3, cv::noArray(), CV_16S);
// 输出矩阵结果
std::cout <<"imag1的数据如下:\n"<<image1 << std::endl<< std::endl;
std::cout <<"imag2的数据如下:\n"<<image2 << std::endl<< std::endl;
std::cout <<"imag3的数据如下:\n"<<image3 << std::endl<< std::endl;
std::cout <<"经过函数subtract()运算后imag3的数据类型为:\n"<<image3.depth() << std::endl<< std::endl;
return 0;
}
运行结果如下:
在OpenCV的数据类型中:
0代表CV_8U - 8-bit unsigned integers ( 0..255 )
1代表CV_8S - 8-bit signed integers ( -128..127 )
2代表CV_16U - 16-bit unsigned integers ( 0..65535 )
3代表CV_16S - 16-bit signed integers ( -32768..32767 )
4代表CV_32S - 32-bit signed integers ( -2147483648..2147483647 )
5代表CV_32F - 32-bit floating-point numbers ( -FLT_MAX..FLT_MAX, INF, NAN )
6代表CV_64F - 64-bit floating-point numbers ( -DBL_MAX..DBL_MAX, INF, NAN )
③ 官方文档对这个函数的描述中有下面这句话:
“Saturation is not applied when the output array has the depth CV_32S. ”
这句话的意思是说如果output array 的数据类型为CV_32S,那么Saturation运算不会被执行。但我按下面的代码测试没有效果:
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <iostream>
int main( )
{
cv::Mat image1( 3, 3, CV_8UC1, cv::Scalar(5) );
cv::Mat image2( 3, 3, CV_8UC1, cv::Scalar(10) );
cv::Mat image3( 3, 3, CV_32SC1,cv::Scalar(0));
cv::subtract(image1, image2, image3, cv::noArray());
// 输出矩阵结果
std::cout <<"imag1的数据如下:\n"<<image1 << std::endl<< std::endl;
std::cout <<"imag2的数据如下:\n"<<image2 << std::endl<< std::endl;
std::cout <<"imag3的数据如下:\n"<<image3 << std::endl<< std::endl;
return 0;
}
运行结果如下:
可见,虽然img3的数据类型被我设置成了CV_32S,但是还是执行了Saturation运算,如果没有执行Saturation运算,img3中的元素值应该是-5才对。所以,这种方法大家就不要用了。
5 函数subtract()可以设置掩码矩阵,被掩码的元素不参与相减运算,这一点在实现很多算法时对于我们是十分方便的。