OpenCV图像基础操作
前面讲了如何导入,索引,遍历,写出图像。这些都是我们要对图像进行操作的基础,今天就讲几个比较基础的图像操作。
图像叠加
我们这里讲的图像叠加就是最简单的线性叠加,仅仅是把两个像素根据各自的权值比重求和即可。
公式如下所示:
算法思想既然这么简单,实际上实现起来也不困难,代码如下:
#include<iostream>
#include<algorithm>
#include<string>
#include<opencv2/opencv.hpp>
#include<opencv2/imgcodecs.hpp>
#include<opencv2/imgproc.hpp>
using namespace std;
using namespace cv;
const string LBJ = "D:\\C++\\kingjames\\Foropencv\\Hazy_Image\\FMVP.jpg";
const string MESSI = "D:\\C++\\kingjames\\Foropencv\\Hazy_Image\\messi.jpg";
void Add(Mat& A, Mat& B, Mat& C);
int main() {
// print out the Info of Img1
Mat A = imread(LBJ);
cout << "图像长宽分别为: " << A.cols << " " << A.rows << endl;
// print out the Info of Img2
Mat B = imread(MESSI);
cout << "图像长宽分别为: " << B.cols << " " << B.rows << endl;
Mat result;
Add(A, B, result);
return 0;
}
void Add(Mat& A, Mat& B, Mat& C) {
cout << A.channels() << " " << B.channels() << endl;
int min_row = min(A.rows, B.rows);
int min_col = min(A.cols, B.cols);
Size sc(min_col, min_row);
C = Mat::zeros(sc, A.type());
double alpha;
cout << "请输入融合系数alpha: ";
cin >> alpha;
// 法一
/*
for (int i = 0; i < min_row; i++) {
const uchar* a = A.ptr<uchar>(i);
const uchar* b = B.ptr<uchar>(i);
uchar* c = C.ptr<uchar>(i);
for (int j = 0; j < min_col * A.channels(); j++) {
*c++ = saturate_cast<uchar>(a[j] * alpha + (1 - alpha) * b[j]);
}
}
*/
// 法二
for (int i = 0; i < min_row; i++) {
for (int j = 0; j < min_col; j++) {
Vec3b a = A.at<Vec3b>(i, j);
Vec3b b = B.at<Vec3b>(i, j);
for (int k = 0; k < A.channels(); k++) {
C.at<Vec3b>(i, j)[k] = saturate_cast<uchar>(alpha * a[k] + (1 - alpha) * b[k]);
}
}
}
// test the result
imshow("result", C);
waitKey(0);
}
实际上略微看一下就知道只是很普通的遍历相加求和赋值操作。
只不过前序需要稍微注意一点,试想如果两张大小不一致的图直接相加会导致什么结果?
我们在遍历时不管三七二十一只要有一张图片没遍历完就接着往下一列抑或是下一行遍历很有可能对于另一张已遍历完的图来说是一个空指针!空指针报错相信大家在DS里已经受够了吧。
于是,事先我们建立的用于存放结果的图就是永远取两者中小的那一个。
**还有一点很值得注意,也是我自己Debug出来的错:声明Size变量时,列数在前(即一个矩形的长),行数在后(矩形的宽)。**这样一来再把此Size用于构造Mat时才会的到我们想要的结果!!
好了,又到了轻松地API环节。
addWeighted函数就是OpenCV帮我们封装好的干上述的函数。
(弱弱说一句,我们的函数adaptivity其实更强呢,因为我们没强制要求两张图的大小相等,但类型是相同的!)
好了,此函数要求我们传入两张待叠加的图**(大小相等,类型相同)**和用于存放结果的矩阵,同时要传入alpha和beta以及gamma。beta = 1 - alpha。gamma是修正偏移量,不要大到过分自己设计达到想要的结果就行,即我们的数学表达式后头再跟一个“+ gamma”。 本人习惯传0。
我自己没啥大小相等的图,那就用教程里的例子(例子的图都在OpenCV文件夹下一个叫做Sample的文件里)。
int main() {
Mat A = imread(samples::findFile("LinuxLogo.jpg"));
Mat B = imread(samples::findFile("WindowsLogo.jpg"));
Mat dst;
double alpha = 0.5;
double beta = 1 - alpha;
addWeighted(A, alpha, B, beta, 0.0, dst);
imshow("Linear Blend", dst);
waitKey(0);
return 0;
}
改变图像对比度与亮度
通常的我们对一个像素点能做的就是线性操作这是简单而实用的。
(当然啦,滤波什么的都正确,但这里提及的是简易的操作)。
如下公式:
g(x) = α * f(x)+ β 中f是原像素点,g是操作后得到的像素点。
剩下的两个系数我们就可以用以控制一张图像的亮度与对比度!
于是我们的操作实际上比上面那个叠加还要简单,主体如下:
for( int y = 0; y < image.rows; y++ ) {
for( int x = 0; x < image.cols; x++ ) {
for( int c = 0; c < image.channels(); c++ ) {
new_image.at<Vec3b>(y,x)[c] =
saturate_cast<uchar>( alpha*image.at<Vec3b>(y,x)[c] + beta );
}
}
}
上述是实现单点操作的具体代码,实际上我们已经接触过一个函数convertTo
我们可以直接写为:
image.convertTo(new_image, -1, alpha, beta);
// 第二个参数的含义实际上是rtype,即本来我们要指定变化成另一种存储模式,
//现在由于不变化,-1就是保持原模式
接下来来分析这两个参数究竟起到了何种作用。
对于beta的理解并不困难,所有像素值都加上一个常值会使得自己本身都变大,这样一来整体就会变亮,这是显而易见的。(附带的影响就是对比度会下降。)
其次对于alpha,如果alpha < 1相当于整个色度域会被压缩。
试想本来是 0 - 255,现在假如是alpha = 0.5,则变为 0 - 127.5,显然整张图的对比图就会下降!
同时alpha会使得一些色度域超过值域而被saturate直接截断,从而丢失了一些像素值影响了图片的真实感和质感。
数字图像处理中一种常见的矫正图像亮度的方法就是gamma矫正,其采用的就是一种非线性的方式。
核心的语句即代表了处理方式:
saturate_cast<uchar>(pow(i / 255.0, gamma_) * 255.0)
显然,i是永远不超过255的,那么当指数函数底数小于1时,假如gamma大于1那么得到的值就会比原先的小,反之gamma小于1时就会增加全图的intensity从而提高亮度。
离散傅里叶变换
傅里叶变换高数学过一点点忘得也差不多了,总之傅里叶变换用在图像领域的作用就是把一张图像从空域到频域。
其核心思想在于所有函数都可以被近似地表示为无穷的由sin和cos组成的式子。
我不想做这个了,所以,我鸽了。