南京市浦口区建设局网站,多商户商城服务态度好,中铁建设集团有限公司门户网登录入口,凡科网站怎样做Part11. 图像的算术运算 图像的本质是一个矩阵#xff0c;所以可以对它进行一些常见的算术运算#xff0c;例如加、减、乘、除、平方根、对数、绝对值等等。除此之外#xff0c;还可以对图像进行逻辑运算和几何变换。 我们先从简单的图像加、减、逻辑运算开始介绍。后续会有… Part11. 图像的算术运算 图像的本质是一个矩阵所以可以对它进行一些常见的算术运算例如加、减、乘、除、平方根、对数、绝对值等等。除此之外还可以对图像进行逻辑运算和几何变换。 我们先从简单的图像加、减、逻辑运算开始介绍。后续会有专门的内容介绍图像的几何变换等。 11.1 图像加法 图像的加法是将两个大小、类型相同的图像按照逐个像素进行相加最后得到一个新的图像。 图像的加、减、乘、除运算都是两个大小、类型相同的图像进行运算。 1.1.1 加法的例子 图像相加的公式 也可以使用dst src1其中 是 C 可重载的运算符。 举个简单的例子 Mat a imread(.../cat.jpg);// 加载了一张猫的图片
imshow(a, a);Mat b Mat(Size(a.cols,a.rows),a.type(), Scalar(0,0,255));// 生成跟a大小类型一样红色的图像Mat c;
cv::add(a,b,c);// 将 a、b 相加结果为c
imshow(c, c); add.png 上述代码中 Mat 对象 c 是 Mat 对象 a、b 相加得到的产物。如果将 b 改成白色也就是 Scalar(255,255,255)。那么 c 会变成什么呢答案依然是白色。因为加法是像素相加如果两个像素点超出255那么依旧会变成255。 1.1.2 实现 add() 函数的功能 为了解释上面的问题我们尝试自己实现一个 add 函数的功能。 Mat a imread(.../cat.jpg); // 加载 cat 的图像
imshow(a, a);Mat b Mat(Size(a.cols,a.rows),a.type(), Scalar(0,0,255));int h a.rows; // 图像 a 的高
int w a.cols; // 图像 a 的宽Mat c Mat::zeros(a.size(), a.type());
for (int row 0; row h; row)
{for (int col 0; col w; col){Vec3b p1 a.atVec3b(row, col);Vec3b p2 b.atVec3b(row, col);c.atVec3b(row, col)[0] saturate_castuchar(p1[0] p2[0]);c.atVec3b(row, col)[1] saturate_castuchar(p1[1] p2[1]);c.atVec3b(row, col)[2] saturate_castuchar(p1[2] p2[2]);}
}imshow(c, c); 通过2层for循环遍历 a、b 图像的每个像素点并将结果相加赋值给 c 图像对应的像素点。在相加的时候使用了 saturate_cast() 函数。 saturate_cast() 是一个模版函数它的作用是防止溢出。它支持 uchar、short、int、float、double 等各种类型。 对于 uchar 类型如果像素值超过255使用 saturate_cast() 函数后它的值变为255。这也正好解释了如果 b 是白色那么最终得到的 c 对象也会是白色。 1.1.3 使用 copyTo() 函数实现的图像叠加 前面的文章我们曾介绍过 copyTo() 函数它可以将 Mat 对象拷贝到另一个 Mat 对象上。 现在再来回顾一下它的使用 Mat a imread(.../cat.jpg); // 加载 cat 的图像Mat b imread(.../leaf.png); // 加载一张小尺寸的树叶的图像Mat roi a(Rect(0,0,b.cols,b.rows));b.copyTo(roi);imshow(result, a); 在上述代码中 roi 对象是从 a 对象中截取一块区域并且该区域跟 b 对象大小一样。由于提取 roi 的操作是浅拷贝将 b 对象复制到 roi 对象之后就会改变 a 对象本身。 下面是执行的结果 copyTo.png 因此可以借助 copyTo() 函数来实现图像的叠加。 21.2 图像的线性混合(linear blending) 图像的线性混合公式$$dst src1alpha src2beta gamma$$ 其中alpha、beta 分别表示图像1和图像2的权重gamma 是亮度调节量。当 alpha beta 1 且 gamma 0 时表示两个图像的相加。 进行线性混合的两个图像也必须大小和类型一致。 Mat a imread(.../cat.jpg); // 加载 cat 的图像Mat b imread(.../chinese_flag.png); // 加载五星红旗的图像resize(a, a,Size(b.cols,b.rows));// 缩放a的大小跟b保持一致Mat dst;
addWeighted(a, 0.5, b, 0.5,0, dst);imshow(dst, dst); 由于图像 a、b 大小不一样因此在线性混合之前需要用 resize() 函数将图像 a 的大小按照图像 b 的大小进行缩放。 linear_lending.png 上面的代码将猫和五星红旗完成了线性混合。如果还想尝试做一个国庆版本的渐变头像则需要离红旗越近红旗的权重越大。 我们可以这样写代码 Mat a imread(.../cat.jpg); // 加载 cat 的图像Mat flag imread(.../chinese_flag.png);
int flag_width flag.cols;
int flag_height flag.rows;Mat dst;resize(a, dst, Size(flag_width, flag_height));int radius 0;
if (flag_width flag_height) {radius flag_width;
} else {radius flag_height;
}for (int i0; i dst.rows; i) {for (int j0; j dst.cols; j) {int distance std::sqrt(i*ij*j);double alpha;if (distance radius) {alpha 1;} else {alpha (double) distance / radius;}double beta 1 - alpha;Vec3b v1 dst.atVec3b(i, j);dst.atVec3b(i, j)[0] alpha * v1[0] beta * flag.atVec3b(i, j)[0];dst.atVec3b(i, j)[1] alpha * v1[1] beta * flag.atVec3b(i, j)[1];dst.atVec3b(i, j)[2] alpha * v1[2] beta * flag.atVec3b(i, j)[2];}
}imshow(dst, dst); avatar.png 31.3 图像减法 图像相减是两个图像按照逐个像素进行相减图像相减可以检测出两个图像的差异。利用这个差异可以做各种检测因此图像减法在很多领域都有实际的用途。 图像相减的公式 也可以使用dst - src1其中 - 是 C 可重载的运算符。 举个简单的例子 Mat a imread(.../cat.jpg); // 加载 cat 的图像int width a.cols;
int height a.rows;Mat b Mat(Size(width,height), a.type(),Scalar(0,0,0));
circle(b, Point(width/2, height/2), 600, Scalar(255,255,255), -1);Mat dst;
subtract(a,b,dst);imshow(dst, dst); subtract.png 上述执行的结果是图像 a 减去图像 b 之后得到的结果将中间的猫“抠掉”了。如果只想要中间的猫而不要背景该怎么做呢本文后续会用 bitwise_and 运算来获取。 再举个例子对加载图像进行高斯模糊然后用原图减去高斯模糊后的图会得到两张图像的差异。 Mat a imread(.../cat.jpg); // 加载 cat 的图像
imshow(a,a);Mat b;
GaussianBlur(a, b,Size(15,15),0,0);
imshow(b,b);Mat dst;
subtract(a,b,dst);
imshow(dst,dst); diff.png 图像的减法介绍完之后图像的乘法(multiply)、除法(divide)、差的绝对值(absdiff)的用法都很类似在实际工作中也经常会用到。特别是 absdiff() 函数用公式表示可以用它获取 差分图经常应用在视频分析中。 Part22. 图像的逻辑运算 42.1 掩模的基础知识 在介绍图像的逻辑运算之前再来回顾一下掩模(mask)的知识因为 OpenCV 很多的函数中都会用到 mask 这个参数。 图像的算术运算、逻辑运算都支持 mask。 掩模是小于或等于源图像的单通道矩阵掩模中的值分为 0 和非 0。 图像掩模是用选定的图像、图形或物体对处理的图像全部或局部进行遮挡来控制图像处理的区域或处理过程。 掩模的作用 提取 ROI屏蔽作用提取结果特征制作特殊形状的图像 掩模的生成方式有很多种。 我们可以自己创建一个将图像减法的第一个例子图像 b 稍微改一下即可。因为 mask 是单通道的矩阵。 Mat mask Mat(Size(width,height), CV_8UC1,Scalar(0,0,0));
circle(mask, Point(width/2, height/2), 600, Scalar(255,255,255), -1); 我们也可以通过图像二值化阈值分割来提取 mask例如 Mat src imread(.../leaf.png); // 加载一张小尺寸的树叶的图像
imshow(src,src);Mat gray;
cvtColor(src,gray,COLOR_BGR2GRAY);Mat mask;
threshold(gray, mask, 0, 255, THRESH_BINARY_INV|THRESH_OTSU);imshow(mask,mask); mask.png 图像二值化的相关内容后续文章会专门介绍。总之mask 的制作有很多方式。 52.2 逻辑运算 两个图像可以进行与、或、异或等逻辑运算。下面是逻辑操作的真值表 aba AND ba OR ba XOR bNOT a000001010111100110111100 其中 与运算的原理如果 a、b 两个值有0则与的结果为0如果 a、b 全为1则与的结果为1。或运算的原理如果 a、b 两个值有1则或的结果为1如果 a、b 全为0则与或的结果为0。异或运算的原理如果 a、b 两个值不相同则异或结果为1如果 a、b 两个值相同则异或结果为0。非运算的原理如果 a 的值为1则非运算的结果为0如果 a 的值为0则非运算的结果为1。 图像的逻辑运算也需要两个大小、类型相同的图像才能进行运算。 Mat a imread(.../cat.jpg); // 加载 cat 的图像Mat b Mat(Size(a.cols,a.rows),a.type(), Scalar(0,0,255));// 生成跟a大小类型一样红色的图像Mat dst1,dst2,dst3,dst4;
bitwise_and(a,b,dst1);
bitwise_or(a,b,dst2);
bitwise_xor(a,b,dst3);
bitwise_not(a,dst4);imshow(bitwise_and, dst1);
imshow(bitwise_or, dst2);
imshow(bitwise_xor, dst3);
imshow(bitwise_not, dst4); bitwise_op.png OpenCV 中的逻辑与、或、异或、非运算对应的函数分别是 bitwise_and、bitwise_or、bitwise_xor、bitwise_not。上图也分别展示了这些函数的执行结果。 现在我们来回答一下前面的问题如何只“抠掉”中间的猫答案是只要使用 bitwise_and 函数即可。 Mat a imread(.../cat.jpg); // 加载 cat 的图像int width a.cols;
int height a.rows;Mat b Mat(Size(width,height), a.type(),Scalar(0,0,0));
circle(b, Point(width/2, height/2), 600, Scalar(255,255,255), -1);Mat dst;
bitwise_and(a,b,dst);
imshow(dst, dst); bitwise_and.png 62.3 利用 mask 进行图像融合 对刚才的代码稍微改动一下把图像 b 的类型改成 CV_8UC1 之后并改名成 mask。bitwise_and 函数的使用也稍作调整。当 mask 参与 bitwise_and 运算的时候执行的结果跟刚才是一致的。 Mat a imread(.../cat.jpg); // 加载 cat 的图像int width a.cols;
int height a.rows;Mat mask Mat(Size(width,height), CV_8UC1,Scalar(0,0,0));
circle(mask, Point(width/2, height/2), 600, Scalar(255,255,255), -1);Mat dst;
bitwise_and(a,a, dst,mask);
imshow(dst, dst); 因为当 bitwise_and 函数使用 mask 参数时该运算只会在掩模值非空的像素点执行。所以可以用来去除背景提取 ROI。 利用 mask 进行“逻辑与”运算即掩膜图像白色区域是对需要处理图像像素的保留黑色区域则是对需要处理图像像素的剔除其余逻辑操作原理类似只是效果不同而已。 之前使用 copyTo() 函数实现的图像叠加生成的图片效果并不理想因为树叶不是透明的。 下面尝试一下将两张图像完美的融合。 Mat a imread(.../cat.jpg); // 加载 cat 的图像Mat b imread(.../leaf.png); // 加载一张小尺寸的树叶的图像Mat b2gray;
cvtColor(b,b2gray,COLOR_BGR2GRAY); // 对 b 转换成灰度图像
imshow(b2gray, b2gray);Mat mask,mask_inv;
threshold(b2gray, mask, 0, 255, THRESH_BINARY_INV|THRESH_OTSU);// 二值分割获取 mask
imshow(mask, mask);bitwise_not(mask,mask_inv);
imshow(mask_inv, mask_inv);Mat roi a(Rect(0,0,b.cols,b.rows));
Mat fg,bg;
bitwise_and(roi,roi,bg, mask_inv);
imshow(bg, bg); // 提取 roi 的背景
bitwise_and(b,b,fg,mask);
imshow(fg, fg); // 提取 b 的前景Mat dst;
add(bg,fg,dst);
dst.copyTo(roi);imshow(result, a); 首先加载两张图像分别为 a、b 对象。 将 b 对象转换成灰度图像然后通过二值分割获取 mask以及对 mask 进行非运算获得 mask_inv。 对 a 对象进行截取 roi 的操作roi 的大小跟 b 对象一致。 然后分别用 与运算 提取 roi 的背景和 b 对象的前景。将两者相加并将结果拷贝到 roi 对象上。最后我们可以看到两张图像完美融合的结果。 下面的几张图分别展示了代码中各个阶段生成的对象以及最后的结果。 step1.png step2.png result.png Part33. 总结 本文分成两个部分。第一部分介绍了图像的算术运算主要是介绍了图像加法、减法以及它们的实现原理和使用场景还介绍了图像的线性混合。 第二部分介绍了图像的逻辑运算回顾了 mask 的用途以及如何在 bitwise_and 函数中使用 mask。