第2章 像素操作
2.2 访问像素
1、 基本原理
图像本质上是一个矩阵,根据行号和列号访问矩阵中的每一个像素,若是单通道图像,可返回单个数值,若是多通道,则返回数值向量。本实例中,仅实现椒盐噪声,表达像素操作的过程。需要注意的是,椒噪声与盐噪声是不同的。
2、代码实现
/**
* 文件名:example_salt.cpp
* 日期:2021/03/28
* 作者:南风无良
*/
#include <random>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
void salt(Mat image, int n);
int main()
{
// 加载目标图像
Mat image = imread("images/cow.jpg");
imshow("Image", image);
// 生成椒盐噪声
salt(image, 6000);
// 显示结果,并保存
imshow("salt", image);
imwrite("output/salt.jpg", image);
waitKey(0);
return 0;
}
void salt(Mat image, int n)
{
default_random_engine generator;
uniform_int_distribution<int> randomRow(0, image.rows - 1);
uniform_int_distribution<int> randomCol(0, image.cols - 1);
int i, j;
for (int k = 0; k < n; k++)
{
i = randomCol(generator);
j = randomRow(generator);
if (image.type() == CV_8UC1) // 单通道
{
image.at<uchar>(j, i) = 255;
}
else if (image.type() == CV_8UC3) // 多通道
{
image.at<Vec3b>(j, i)[0] = 255;
image.at<Vec3b>(j, i)[1] = 255;
image.at<Vec3b>(j, i)[2] = 255;
}
}
}
3、验证结果
(1)原图像
(2)椒盐噪声
2.3 用指针扫描图像
1、基本原理
本实例的目的是为了提高像素访问的效率,试验任务是减少图像中的颜色数量,或者灰度级。
2、代码实现
/**
* 文件名:example_colorReduce.cpp
* 日期:2021/03/28
* 作者:南风无良
*/
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
void colorReduce(Mat image, int n);
int main(int argc, char* argv[])
{
// 加载目标图像
Mat image = imread("images/cow.jpg");
imshow("Image", image);
// 缩减颜色等级
colorReduceIter(image, 64);
// 显示结果,并保存
imshow("colorReduce", image);
imwrite("output/colorReduceIter64.jpg", image);
waitKey(0);
return 0;
}
void colorReduce(cv::Mat image, int div = 64)
{
int nl = image.rows;
int nc = image.cols * image.channels(); // 每行的像素数量
for (int j = 0; j < nl; j++)
{
uchar* data = image.ptr<uchar>(j); // 获取行j的首地址
for (int i = 0; i < nc; i++)
{
data[i] = data[i] / div * div + div / 2;
}
}
}
3、验证结果
(1)原图像
(2)效果图
2.4 用迭代器扫描图像
1、基本原理
迭代器是一种类型,用于遍历容器中的每一个元素,并且隐藏具体细节,本例就是利用这样的类对图像进行遍历操作。
2、代码实现
/**
* 文件名:example_colorReduceIter.cpp
* 日期:2021/03/28
* 作者:南风无良
*/
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
void colorReduceIter(Mat image, int n);
int main(int argc, char* argv[])
{
// 加载目标图像
Mat image = imread("images/cow.jpg");
imshow("Image", image);
// 缩减颜色等级
colorReduceIter(image, 64);
// 显示结果,并保存
imshow("colorReduce", image);
imwrite("output/colorReduceIter64.jpg", image);
waitKey(0);
return 0;
}
void colorReduceIter(Mat image, int div = 64)
{
int n = static_cast<int>(log(static_cast<double>(div) / (log(2.0) + 0.5)));
uchar mask = 0XFF << n;
uchar div2 = div >> 1;
// 迭代器
Mat_<Vec3b>::iterator it = image.begin<Vec3b>();
Mat_<Vec3b>::iterator itend = image.end<Vec3b>();
// 扫描图像
for (; it != itend; it++)
{
(*it)[0] &= mask;
(*it)[0] += div2;
(*it)[1] &= mask;
(*it)[1] += div2;
(*it)[2] &= mask;
(*it)[2] += div2;
}
}
3、验证结果
(1)原图像
(2)效果图
2.5 编写高效的图像扫描循环
1、基本原理
在编写图像处理函数时,您需要充分考虑运行效率。在设计函数时,您要经常检查代码的运行效率,找出处理过程中可能使程序变慢的瓶颈。但是有一点非常重要,除非确实必要,不要以牺牲代码的清晰度来优化性能。简洁的代码总是更容易调试和维护。只有对程序效率至关重要的代码段,才需要进行重度优化。
2、代码实现
/**
* 文件名:example_colorReduceIter2.cpp
* 日期:2021/03/29
* 作者:南风无良
*/
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
void colorReduceIter(Mat image, int n);
int main(int argc, char* argv[])
{
// 加载目标图像
Mat image = imread("images/cow.jpg");
imshow("Image", image);
// 缩减颜色等级
const int64 start = getTickCount();
colorReduceIter(image, 64);
double duration = (getTickCount() - start) / getTickFrequency();
// 显示结果,并保存
imshow("colorReduce", image);
imwrite("output/colorReduceIter64.jpg", image);
waitKey(0);
cout << "colorReduce: " << duration << "s" << endl;
return 0;
}
void colorReduceIter(Mat image, int div = 64)
{
int n = static_cast<int>(log(static_cast<double>(div) / (log(2.0) + 0.5)));
uchar mask = 0XFF << n;
uchar div2 = div >> 1;
// 迭代器
Mat_<Vec3b>::iterator it = image.begin<Vec3b>();
Mat_<Vec3b>::iterator itend = image.end<Vec3b>();
// 扫描图像
for (; it != itend; it++)
{
(*it)[0] &= mask;
(*it)[0] += div2;
(*it)[1] &= mask;
(*it)[1] += div2;
(*it)[2] &= mask;
(*it)[2] += div2;
}
}
3、验证结果
colorReduce: 0.017201s
2.6 扫描图像并访问相邻像素
1、基本原理
通俗来说,就是手写滤波器。本实例中的滤波器,是一个锐化滤波器。
2、滤波器
[[0, -1, 0];
[-1, 5, -1];
[0, -1, 0]]
2、代码实现
/**
* 文件名:example_sharpen2D.cpp
* 日期:2021/03/28
* 作者:南风无良
*/
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
void sharpen2D(const Mat& image, Mat& result);
int main(int argc, char* argv[])
{
// 加载目标图像
Mat image = imread("images/cow.jpg");
imshow("Image", image);
Mat result;
// 图像锐化
const int64 start = getTickCount();
sharpen2D(image, result);
double duration = (getTickCount() - start) / getTickFrequency();
// 显示结果,并保存
imshow("sharpen2D", result);
imwrite("output/sharpen2D.jpg", result);
waitKey(0);
cout << "colorReduce: " << duration << "s" << endl;
return 0;
}
void sharpen2D(const Mat& image, Mat& result)
{
// 构造内核(所有入口都初始化为0)
Mat kernel(3, 3, CV_32F, Scalar(0));
/* 同态滤波 */
kernel.at<float>(1, 1) = 5.0;
kernel.at<float>(0, 1) = -1.0;
kernel.at<float>(1, 0) = -1.0;
kernel.at<float>(1, 2) = -1.0;
kernel.at<float>(2, 1) = -1.0;
Mat kernel2(3, 3, CV_32F, Scalar(0));
kernel2.at<float>(1, 1) = 4.0;
kernel2.at<float>(0, 1) = -1.0;
kernel2.at<float>(1, 0) = -1.0;
kernel2.at<float>(1, 2) = -1.0;
kernel2.at<float>(2, 1) = -1.0;
// 对图像滤波
filter2D(image, result, image.depth(), kernel2);
}
3、验证结果
(1)原图像
(2)效果图
2.7 实现简单的图像运算
1、基本原理
图像的运算,也就是基本的矩阵运算,加、减、乘和除,以及取模。一般有两种方式,(1)通过OpenCV提供的add函数;(2)或者通过直接的矩阵算术运算,但是需要注意阈值范围。
2、代码实现
/**
* 文件名:example_addImages.cpp
* 日期:2021/03/28
* 作者:南风无良
*/
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
void addImages(const Mat& image1, float weight1, const Mat& image2, float weight2, float gamma, Mat& result);
int main(int argc, char* argv[])
{
// 加载目标图像
Mat image = imread("images/cow.jpg");
imshow("Image", image);
cout << "cow: " << image.size() << endl;
// 加载背景图像,并缩放至与原目标图像大小
Mat image2 = imread("images/rain.jpg");
resize(image2, image2, Size(700, 450));
imshow("rain", image2);
Mat result;
// 将目标图像与背景图像相加
const int64 start = getTickCount();
addImages(image, 0.9, image2, 0.4, 0.5, result);
double duration = (getTickCount() - start) / getTickFrequency();
// 显示结果,并保存
imshow("addImages", result);
imwrite("output/addImages.jpg", result);
waitKey(0);
cout << "addImges: " << duration << "s" << endl;
return 0;
}
void addImages(const cv::Mat& image1, float weight1, const cv::Mat& image2, float weight2, float gamma, cv::Mat& result)
{
if (image1.rows * image1.cols == 0 || image2.rows * image2.cols == 0)
{
std::cout << "Empty Image!" << std::endl;
return ;
}
cv::addWeighted(image1, weight1, image2, weight2, gamma, result);
}
3、验证结果
(1)目标图像
(2)背景图像
(3)相加结果
2.8 图像重映射
1、基本原理
通过移动图像像素,改变图像的外在表现,可以做出一些很炫酷的效果。
2、代码实现
/**
* 文件名:example_wave.cpp
* 日期:2021/03/28
* 作者:南风无良
*/
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
void wave(const Mat& image, Mat& result);
int main(int argc, char* argv[])
{
// 加载目标图像
Mat image = imread("images/cow.jpg");
imshow("Image", image);
cout << "cow: " << image.size() << endl;
Mat result;
// 图像重映射
const int64 start = getTickCount();
wave(image, result);
double duration = (getTickCount() - start) / getTickFrequency();
// 显示结果,并保存
imshow("remapImage", result);
imwrite("output/remapImage.jpg", result);
waitKey(0);
cout << "wave: " << duration << "s" << endl;
return 0;
}
void wave(const Mat& image, Mat& result)
{
// 映射参数
Mat srcX(image.rows, image.cols, CV_32F);
Mat srcY(image.rows, image.cols, CV_32F);
// 创建映射参数
for (int i = 0; i < image.rows; i++)
{
for (int j = 0; j < image.cols; j++)
{
srcX.at<float>(i, j) = j;
srcY.at<float>(i, j) = i + 5 * sin(j / 10.0);
}
}
// 应用映射参数
remap(image, result, srcX, srcY, INTER_LINEAR);
}
3、验证结果
(1)原图像
(2)重映射图像
参考文献
[1]《OpenCV计算机视觉编程攻略(第3版)》