一、图像运算
针对图像的加法运算、位运算都是比较基础的运算。但是,很多复杂的图像处理功能正是借助这些基础的运算来完成的。所以,牢固掌握基础操作,对于更好地实现图像处理是非常有帮助的。本章简单介绍了加法运算、位运算,并使用它们实现了位平面分解、图像异或加密、数字水印、脸部打码/解码等实例。
1.图像加法运算
在图像处理过程中,经常需要对图像进行加法运算。可以通过加号运算符“+”对图像进行加法运算,在c++中也可以利用cv::add()对图像进行加法运算。
通常情况下,在灰度图像中,像素用8个比特位(一个字节)来表示,像素值的范围是[0,255]。两个像素值在进行加法运算时,求得的和很可能超过255。上述两种不同的加法运算方式,对超过255的数值的处理方式是不一样的。
加号运算符
使用加号运算符“+”对图像a(像素值为a)和图像b(像素值为b)进行求和运算时,遵循以下规则:
式中,mod()是取模运算,“mod(a+b, 256)”表示计算“a+b的和除以256取余数”。
根据上述规则,两个点进行加法运算时:
● 如果两个图像对应像素值的和小于或等于255,则直接相加得到运算结果。例如,像素值28和像素值36相加,得到计算结果64。
● 如果两个图像对应像素值的和大于255,则将运算结果对256取模。例如255+58=313,大于255,则计算(255+58)% 256=57,得到计算结果57。
当然,上述公式也可以简化为a+b=mod(a+b,256),在运算时无论相加的和是否大于255,都对数值256取模。
【例4.4.1】假设我们有两个8位灰度图像,图像a的像素值为a,图像b的像素值为b。
1.像素值a为28,像素值b为36。
2.像素值a为255,像素值b为58。
#include <iostream>
#include <cmath>
int main() {
// 案例1: 像素值a为28, 像素值b为36
int a1 = 28;
int b1 = 36;
int result1 = a1 + b1; // 结果直接相加
std::cout << "a1 + b1 = " << result1 << std::endl; // 输出64
int result1_mod = result1 % 256; // 对256取模
std::cout << "mod(a1+b1, 256) = " << result1_mod << std::endl; // 输出64 (因为28+36小于或等于255)
案例2: 像素值a为255, 像素值b为58
int a2 = 255;
int b2 = 58;
int result2 = a2 + b2; // 结果直接相加
std::cout << "a2 + b2 = " << result2 << std::endl; // 输出313
int result2_mod = result2 % 256; // 对256取模
std::cout << "mod(a2+b2, 256) = " << result2_mod << std::endl; // 输出57 (因为255+58大于255)
return 0;
}
上述代码演示了如何根据给定的规则对两个图像的像素值进行加法运算。对于每个案例,它首先计算直接相加的结果,然后计算对256取模的结果,以验证规则的正确性。
【例4.4.2】在c++中分别使用加号运算符和opencv库中的函数cv2.add()计算两幅灰度图像的像素值之和,观察处理结果。
#include <opencv2/opencv.hpp>
#include <opencv2/highgui/highgui.hpp>
int main() { // 读取图像
cv::Mat a = cv::imread("lena.bmp", cv::IMREAD_GRAYSCALE);
if (a.empty()) { std::cout << "无法读取图像" << std::endl; return -1; } // 创建图像的副本
cv::Mat b = a.clone(); // 计算图像的和
cv::Mat result1;
cv::add(a, b, result1); // 使用函数cv2.add()计算a和b之和
cv::Mat result2 = a + b; // 使用加号运算符计算a和b之和 // 显示原始图像和结果
cv::namedWindow("Original",cv::WINDOW_NORMAL);
cv::imshow("Original",a);
cv::namedWindow("Result1", cv::WINDOW_NORMAL);
cv::imshow("Result1", result1);
cv::namedWindow("Result2",cv::WINDOW_NORMAL);
cv::imshow("Result2", result2);
cv::waitKey(0);
cv::destroyAllWindows();
return 0;
}
在本例中,首先读取了图像lena并将其标记为变量a;接下来,使用语句“b=a”将图像lena复制到变量b内;最后,分别使用“+”cv::add()计算a和b之和。
运行程序,得到如图3-1所示的运行结果,其中:
图4-11 【例4.4.2】程序运行结果图
● 左图是原始图像lena。
● 中间的图是使用加号运算符将图像lena自身相加的结果。
● 右图是使用函数cv2.add()将图像lena自身相加的结果。从上述运算结果可以看出
● 使用加号运算符计算图像像素值的和时,将和大于255的值进行了取模处理,取模后大于255的这部分值变得更小了,导致本来应该更亮的像素点变得更暗了,相加所得的图像看起来并不自然。
● 使用函数cv::add()计算图像像素值的和时,将和大于255的值处理为饱和值255。图像像素值相加后让图像的像素值增大了,图像整体变亮。
2.图像加权和
所谓图像加权和,就是在计算两幅图像的像素值之和时,将每幅图像的权重考虑进来,可以用公式表示为:
式中,saturate()表示取饱和值(最大值)。图像进行加权和计算时,要求src1和src2必须大小、类型相同,但是对具体是什么类型和通道没有特殊限制。它们可以是任意数据类型,也可以有任意数量的通道(灰度图像或者彩色图像),只要二者相同即可。
OpenCV 库中提出了一个addWeighted()函数,用于执行两个图像的加权和操作。这个函数可以用来创建一幅新的图像,其中包含了两幅输入图像的加权组合。通常,这在图像融合和混合的应用中非常有用,该函数的语法格式为:
void cv::addWeighted(
cv::InputArray src1, // 第一个输入图像
double alpha, // 第一个输入图像的权重
cv::InputArray src2, // 第二个输入图像
double beta, // 第二个输入图像的权重
double gamma, // 加权和的可选标量
cv::OutputArray dst, // 输出图像
int dtype = -1 // 输出图像的数据类型,默认为-1(与输入相同)
);
其中,参数alpha和beta是src1和src2所对应的系数,它们的和可以等于1,也可以不等于1。该函数实现的功能是dst = src1×alpha + src2×beta + gamma。需要注意,式中参数gamma的值可以是0,但是该参数是必选参数,不能省略。可以将上式理解为“结果图像=图像1×系数1+图像2×系数2+亮度调节量”。
参数说明:
- src1:第一个输入图像(可以是 cv::Mat 类型或类似的数据结构)。
- alpha:第一个输入图像的权重,一个双精度浮点数。
- src2:第二个输入图像。
- beta:第二个输入图像的权重,也是一个双精度浮点数。
- gamma:加权和的可选标量,通常是一个双精度浮点数。
- dst:输出图像,这里将存储加权和的结果。
- dtype:输出图像的数据类型,默认为-1,表示与输入图像的数据类型相同。
【例4.4.3】使用函数cv::addWeighted()对两幅图像进行加权混合,观察处理结果。根据题目要求,编写程序如下:
#include <opencv2/opencv.hpp>
int main() {
// 读取图像
cv::Mat a = cv::imread("boat.bmp");
cv::Mat b = cv::imread("lena.bmp"); // 检查图像是否正确读取
if (a.empty() || b.empty()) {
std::cout << "无法读取图像文件!" << std::endl;
return -1;
}
// 计算加权和
cv::Mat result;
cv::addWeighted(a, 0.6, b, 0.4, 0, result); // 显示图像
cv::namedWindow("boat", cv::WINDOW_NORMAL);
cv::imshow("boat", a);
cv::namedWindow("lena", cv::WINDOW_NORMAL);
cv::imshow("lena", b);
cv::namedWindow("result", cv::WINDOW_NORMAL);
cv::imshow("result", result); // 等待按键,然后关闭窗口
cv::waitKey(0); cv::destroyAllWindows();
return 0;
}
本程序使用cv2.addWeighted()函数,对图像boat和图像lena分别按照0.6和0.4的权重进行混合。运行程序,得到如图4-12所示的结果,其中:
● 左图是原始图像boat。
● 中间的图是原始图像lena。
● 右图是图像boat和图像lena加权混合后的结果图像。
图4-12 【例4.4.3】程序的运行结果