1. 图画的算术运算

图画的实质是一个矩阵,所以能够对它进行一些常见的算术运算,例如加、减、乘、除、平方根、对数、绝对值等等。除此之外,还能够对图画进行逻辑运算和几许改换。

咱们先从简单的图画加、减、逻辑运算开端介绍。后续会有专门的内容介绍图画的几许改换等。

1.1 图画加法

图画的加法是将两个巨细、类型相同的图画依照逐一像素进行相加,最终得到一个新的图画。

图画的加、减、乘、除运算,都是两个巨细、类型相同的图画进行运算。

1.1.1 加法的比如

图画相加的公式:dst=src1+src2 dst = src1 + src2

也能够运用: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);

OpenCV 笔记(4):图画的算术运算、逻辑运算

上述代码中 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.at<Vec3b>(row, col);
        Vec3b p2 = b.at<Vec3b>(row, col);
        c.at<Vec3b>(row, col)[0] = saturate_cast<uchar>(p1[0] + p2[0]);
        c.at<Vec3b>(row, col)[1] = saturate_cast<uchar>(p1[1] + p2[1]);
        c.at<Vec3b>(row, col)[2] = saturate_cast<uchar>(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 目标本身。

下面是履行的成果:

OpenCV 笔记(4):图画的算术运算、逻辑运算

因而,能够凭借 copyTo() 函数来完成图画的叠加。

1.2 图画的线性混合(linear blending)

图画的线性混合公式:dst=src1∗alpha+src2∗beta+gammadst = src1*alpha + src2*beta + 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 的巨细进行缩放。

OpenCV 笔记(4):图画的算术运算、逻辑运算

上面的代码,将猫和五星红旗完成了线性混合。假如还想测验做一个国庆版别的突变头像,则需求离红旗越近,红旗的权重越大。

咱们能够这样写代码:

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 i=0; i < dst.rows; i++) {
    for (int j=0; j < dst.cols; j++) {
        int distance = std::sqrt(i*i+j*j);
        double alpha;
        if (distance > radius) {
            alpha =  1;
        }  else {
            alpha = (double) distance / radius;
        }
        double beta = 1 - alpha;
        Vec3b v1 = dst.at<Vec3b>(i, j);
        dst.at<Vec3b>(i, j)[0]= alpha * v1[0] + beta * flag.at<Vec3b>(i, j)[0];
        dst.at<Vec3b>(i, j)[1]= alpha * v1[1] + beta * flag.at<Vec3b>(i, j)[1];
        dst.at<Vec3b>(i, j)[2]= alpha * v1[2] + beta * flag.at<Vec3b>(i, j)[2];
    }
}
imshow("dst", dst);

OpenCV 笔记(4):图画的算术运算、逻辑运算

1.3 图画减法

图画相减是两个图画依照逐一像素进行相减,图画相减能够检测出两个图画的差异。运用这个差异能够做各种检测,因而图画减法在许多范畴都有实际的用处。

图画相减的公式:dst=src1−src2dst = src1 – src2

也能够运用: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);

OpenCV 笔记(4):图画的算术运算、逻辑运算

上述履行的成果是图画 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);

OpenCV 笔记(4):图画的算术运算、逻辑运算

图画的减法介绍完之后,图画的乘法(multiply)、除法(divide)、差的绝对值(absdiff)的用法都很相似,在实际工作中也常常会用到。特别是 absdiff() 函数,用公式表明:dst=∣src1−src2∣dst = |src1 − src2| 能够用它获取差分图,常常应用在视频剖析中。

2. 图画的逻辑运算

2.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);

OpenCV 笔记(4):图画的算术运算、逻辑运算

图画二值化的相关内容后续文章会专门介绍。总之,mask 的制作有许多方法。

2.2 逻辑运算

两个图画能够进行与、或、异或等逻辑运算。下面是逻辑操作的真值表:

a b a AND b a OR b a XOR b NOT a
0 0 0 0 0 1
0 1 0 1 1 1
1 0 0 1 1 0
1 1 1 1 0 0

其间,

  • 与运算的原理:假如 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);

OpenCV 笔记(4):图画的算术运算、逻辑运算

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);

OpenCV 笔记(4):图画的算术运算、逻辑运算

2.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 目标上。最终,咱们能够看到两张图画完美交融的成果。

下面的几张图别离展现了代码中各个阶段生成的目标,以及最终的成果。

OpenCV 笔记(4):图画的算术运算、逻辑运算

OpenCV 笔记(4):图画的算术运算、逻辑运算

OpenCV 笔记(4):图画的算术运算、逻辑运算

3. 总结

本文分成两个部分。第一部分介绍了图画的算术运算,主要是介绍了图画加法、减法以及它们的完成原理和运用场景,还介绍了图画的线性混合。

第二部分介绍了图画的逻辑运算,回忆了 mask 的用处,以及如何在 bitwise_and 函数中运用 mask。