OpenCV(C++)图像运算
图像在计算机中就是一个普通的数值矩阵存在的,所以也就能够相应的进行各种运算,这些运算构成了图像处理的基本操作。图像加法可以混合两幅图像进行图像融合,比如在处理照片的贴图;图像减法可以用来去掉运动图像的背景,来进行目标定位追踪。这篇文章主要介绍图像的算术运算,逻辑运算,重映射变换等。
代数运算
算术运算包括加、减、乘、除和位运算,这些运算操作的特点是提供两个输入参数,得到一个输出结果。有时候还可以运算操作的权重系数,或者指定掩码。
加法运算拥有多种格式,能够灵活的进行各种不同的加法操作。下面主要介绍几种加法运算的用法。
最普通的加法操作 c[i] = a[i] + b[i] 。
cv::add(imageA,imageB,resultC);
将图像加上一个常数 c[i] = a[i] + k, 注意传入的Scalar是表示颜色的对象,需要根据图像的通道数来具体定义。
//灰度图像
cv::add(imageA,cv::Scalar(k),resultC);
//彩色图像
cv::add(imageA,cv::Scalar(k,i,j),resultC);
将两幅图像进行加权混合 c[i] = k1*a[i] + k2*b[i] + k3 。
cv::addWeighted(imageA,k1,imageB,k2,k3,resultC);
还能进行线性相加 c[i] = k*a[i] + b[i]。
cv::scaleAdd(imageA,k,imageB,resultC);
这些都是图像整体的加法运算,还能够添加一个掩码参数,掩码的作用就是让图像的某一部分参与运算,其他部分保持原样。具体来说,掩码通常与要运算的图像大小一样,当掩码的值非空(即为真),则在对应的图像位置进行运算,否则不运算。
cv::add(imageA,imageB,resultC,mask);
除了加法运算外,还有减法 cv::subtract、乘法 cv::multiply、除法 cv::divede、差的绝对值cv::absdiff,这些函数也有多种格式。
位运算符也是常用的一类运算符,如与运算 cv::bitwise_and、或运算cv::bitwise_or、异或运算 cv::bitwise_xor、非运算 cv::bitwise_not。cv::max 和 cv::min 能够找到两幅图像中的最大或者最小值。 此外还能对单张图像进行数学操作,如 cv::sqrt、cv::pow、cv::abs、cv::exp等等。
值得注意的一点是,图像像素的值范围通常是0-255,所以经过这些操作之后,有可能值会超出这个范围,然后会进行补码运算使值在unsigned char 能表达的范围之内,会造成结果错误的情况。所以记得使用 cv::saturate_cast 函数,这个函数能够把小于0的调整为0,大于255的调整为255,把浮点数调整为最接近的整数,以确保结果在预定的像素范围之内。
重载运算符
大部分的C++中的运算符在Opencv中都进行了重载,所以进行算术运算符时可以直接使用运算符。例如+、 -、 *、 / 和 &、 | 、^ 、~ 都能够直接使用,而比较运算符<、>、==、>=等也进行了重载,运算结果会返回一个8 bit 的二值图像。
C = 4 * A + 7 * B + 8;
C = A >= B;
图像还是一个矩阵,所以矩阵的操作也进行了重载。矩阵乘法A*B,矩阵求逆 A.inv(),矩阵转置 A.t(),求矩阵行列式 A.determinant(),叉乘 A.cross(B),点乘 A.dot(B)。此外,所有复合赋值运算符+=、-=、&=也是可以使用的。
图像通道分割
有时候需要对多通道的图像进行通道分割,对不同的通道进行不同的操作。这时候就可以使用 cv::split 函数,将图像的三个通道分别放到三个Mat对象中。而把三个通道合并可以使用 cv::merge 函数,即合并成一彩色图像。例如把一张图像与蓝色通道进行混合,可以这样实现:
std::vector<cv::Mat> planes;
cv::split(image,planes);
planes[0] += image2;
cv.merge(planes,result);
注意:在Opencv中通道顺序是BGR,蓝色表示channels[0],绿色表示channels[1],红色表示channels[2]。
图像重映射
前面都是改变图像的像素值,如果只想改变像素的位置,可以使用 remap 函数,只需要定义好映射参数,然后将映射参数应用到输入图像上就可以。映射参数分为 X 坐标轴 和 Y 坐标轴,它们都用浮点数类型的 cv::Mat 表示,然后根据某种规则创建映射参数。下面展示一段用remap函数反转图像的代码(虽然已有方法 flip() 可以直接做到) :
using namespace cv;
int main()
{
//变量定义
Mat srcImage, dstImage;
Mat map_x, map_y;
//载入原始图
srcImage = imread("E:\\写作插图\\下载.jpg", 1);
if (!srcImage.data)
{
printf("error\n");
return false;
}
imshow("原始图", srcImage);
//创建和原始图一样的效果图,x重映射图,y重映射图
dstImage.create(srcImage.size(), srcImage.type());
map_x.create(srcImage.size(), CV_32FC1);
map_y.create(srcImage.size(), CV_32FC1);
//双层循环,创建映射参数
for (int j = 0; j < srcImage.rows; j++)
{
for (int i = 0; i < srcImage.cols; i++)
{
//改变map_x & map_y的值.
map_x.at<float>(j, i) = static_cast<float>(srcImage.cols - i);
map_y.at<float>(j, i) = static_cast<float>(j);
}
}
//进行重映射操作
remap(srcImage, dstImage, map_x, map_y, CV_INTER_LINEAR, BORDER_CONSTANT, Scalar(0, 0, 0));
//显示效果
imshow("映射图像", dstImage);
waitKey(0);
return 0;
}
值得注意的是,映射参数是浮点数,所以可以映射到一个非整数值,因此必须进行插值操作,常用的插值操作有INTER_NEAREST - 最近邻插值 ,INTER_LINEAR – 双线性插值(默认值),INTER_CUBIC – 双三次样条插值,这里使用的就是INTER_LINEAR插值方法。
原始图与映射图片的对比
欢迎大家关注公众号“计算机视觉与机器学习”
计算机视觉和机器学习