OpenCV计算机视觉编程攻略(第3版)—— 第2章 操作像素

第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版)》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

南风无良

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值