CV+openCV 之操作图像(中)

1. 用迭代器扫描图像

        【准备工作】使用操作图像(上)的减色程序作为例子。

        【实现】

#include<opencv2/core.hpp>
#include<opencv2/highgui.hpp>
#include<opencv2/imgproc.hpp>
#include<iostream>
#include<random>
using namespace std;
using namespace cv;
void colorReduce(cv::Mat &image, int div = 64) {
	//div必须是2的幂
	int n = static_cast<int>(
		log(static_cast<double>(div)) / log(2.0) + 0.5);
	//用来截取像素值的掩码
	uchar mask = 0xFF << n;//如果div=16,mask=0xF0
	uchar div2 = div >> 1;//div2=div/2
	//迭代器
	cv::Mat_<cv::Vec3b>::iterator it = image.begin<cv::Vec3b>();
	cv::Mat_<cv::Vec3b>::iterator itend = image.end<cv::Vec3b>();

	//扫描全部像素
	for (; it != itend; ++it) {
		(*it)[0] &= mask;
		(*it)[0] += div2;
		(*it)[1] &= mask;
		(*it)[1] += div2;
		(*it)[2] &= mask;
		(*it)[2] += div2;
	}
}
int main()
{
	cv::MatIterator_<cv::Vec3b> it;//或者 cv::Mat_<cv::Vec3b>::iterator it;
	cv::Mat image;
	image = cv::imread("1.jpg");
	//复制图像,深复制最简单的是使用clone
	cv::Mat imageClone = image.clone();
	//处理图像副本
	colorReduce(imageClone, 64);
	cv::namedWindow("Image");
	cv::imshow("Image", imageClone);
	cv::waitKey(0);
}

        【实现原理】

        不管扫描的是哪种类型的集合,使用迭代器时总是遵循同样的模式。

         首先要使用合适的专用类创建迭代器对象,这里是cv::Mat_::iterator(或 cv::MatIterator_)。 然后可以用 begin 方法,在开始位置(本例中为图像的左上角)初始化迭代器。

        对于彩色 图像的 cv::Mat 实例,可以使用 image.begin()。

        还可以在迭代器上使用数学计算,若要从图像第二行开始,可以用 image.begin()+image.cols 初始化 cv::Mat 迭代器。获取集合结束位置的方法也类似,只是改用 end 方法。但是,若用 end 方法得到的迭代器已经超出了集合范围,因此必须在结束位置停止迭代过程。结束的迭代器也能使用数学计算,例如你想在最后一行前就结束迭代,可使用 image.end()-image.cols。 初始化迭代器后,建立一个循环遍历所有元素,到结束迭代器为止。

        典型的 while 循环就 像这样:

while (it!= itend) {
    // 处理每个像素 --------------------- 
    ... 
    // 像素处理结束 --------------------- 
    ++it; }

         可以用运算符++移动到下一个元素,也可以指定更大的步幅。例如 it+=10,对每10个像素处理一次。 最后,在循环内部使用取值运算符*访问当前元素,可以用它来读(例如 element= *it;)或写(例如*it= element;)。也可以创建常量迭代器,用作对常量 cv::Mat 的引用, 或者表示当前循环不修改cv::Mat实例。常量迭代器定义:cv::MatConstIterator_ it; 或者:cv::Mat_::const_iterator it;

        【扩展阅读】

//也可以用对cv::Mat_实例的引用来获取开始与结束位置
cv::Mat_<cv::Vec3b> cimage(image); 
cv::Mat_<cv::Vec3b>::iterator it= cimage.begin(); 
cv::Mat_<cv::Vec3b>::iterator itend= cimage.end();

2. 编写高效的图像扫描循环

        【实现】

    const int64 start = cv::getTickCount();//测算运行时间的函数
	colorReduce(imageClone, 64);//调用函数
	//经过的时间(秒)
	double duration = (cv::getTickCount() - start) / cv::getTickFrequency();
	std::cout << duration;

        【实现原理】

        colorReduce函数使用位运算的方法要比其他方法快得多。使用迭代器的主要目的是简化图像扫描 过程,降低出错的可能性,但是运行时间更长。

        对于可以预先计算的数值,要避免在循环中做重复计算。

        之前还做过连续性测试,针对连续图像生成一个循环,而不是对行和列运行常规的二重循环, 使运行速度平均提高了 10%。

3. 扫描图像并访问相邻像素

        【准备工作】本例将使用锐化图像的处理函数。

        【实现】

#include<opencv2/core.hpp>
#include<opencv2/highgui.hpp>
#include<opencv2/imgproc.hpp>
#include<iostream>
#include<random>
using namespace std;
using namespace cv;
void sharpen(const cv::Mat& image, cv::Mat& result) {
	//判断是否需要分配图像数据,如果需要就分配
	result.create(image.size(), image.type());
	int nchannels = image.channels();//获得通道数
	//处理所有行(除了第一行和最后一行)
	for (int j = 1; j < image.rows - 1; j++) {
		const uchar* previous = image.ptr<const uchar>(j - 1);//上一行
		const uchar* current = image.ptr<const uchar>(j);     //当前行
		const uchar* next = image.ptr<const uchar>(j + 1);    //下一行

		uchar* output = result.ptr<uchar>(j);//输出行

		for (int i = nchannels; i < (image.cols - 1) * nchannels; i++) {
			//应用锐化算子
			*output++ = cv::saturate_cast<uchar>(
				5 * current[i] - current[i - nchannels] -
				current[i + nchannels] - previous[i] - next[i]);
		}
	}
	result.row(0).setTo(cv::Scalar(0));
	result.row(result.rows - 1).setTo(cv::Scalar(0));
	result.col(0).setTo(cv::Scalar(0));
	result.col(result.cols - 1).setTo(cv::Scalar(0));
}
int main()
{
	cv::MatIterator_<cv::Vec3b> it;//或者 cv::Mat_<cv::Vec3b>::iterator it;
	cv::Mat image;
	image = cv::imread("1.jpg");
	//复制图像,深复制最简单的是使用clone
	cv::Mat imageClone = image.clone();
	sharpen(image, imageClone);//调用函数
	cv::namedWindow("Image");
	cv::imshow("Image", imageClone);
	cv::waitKey(0);
}

        【实现原理】

        计算锐化数值的方法:

        sharpened_pixel= 5*current-left-right-up-down;

        只需定义额外的指针,并与当前行的指针一起递增, 然后就可以在扫描循环内访问上下行的指针了。

        调用 cv::saturate_cast 模板函数,并传入运算结果,来计算输出像素的值。因为计算像素的数学表达式的结果经常超出允许的范围(即小于 0 或大于 255)。使用这 函数可把结果调整到 8 位无符号数的范围内,具体做法是把小于 0 的数值调整为 0,大于 255 的 数值调整为 255。

        由于边框上的像素没有完整的相邻像素,因此不能用前面的方法计算。这里简单地把它们设置为 0。有时也可以对这些像素做特殊的计算,但在大多数情况下,花时间处理这些极少数像素是没有意义的。在本例中,用两个特殊的方法把边框的像素设置为了 0,它们是 row 和 col。这两个方法返回一个特殊的 cv::Mat 实例,其中包含一个单行 ROI(或单列 ROI),具体范围取决于参数。这里没有进行复制,因为只要这个一维矩阵的元素被修改,原始图像也会被修改。用 setTo 方法来实现这个功能,此方法将对矩阵中的所有元素赋值: result.row(0).setTo(cv::Scalar(0)); 这个语句把结果图像第一行的所有像素设置为 0。对于三通道彩色图像,需要使用 cv:: Scalar(a,b,c)来指定三个数值,分别对像素的每个通道赋值。

        【扩展阅读】

        用滤波去处理图像,锐化就显得非常简单了。

void sharpen(const cv::Mat& image, cv::Mat& result) {
	// 构造内核(所有入口都初始化为 0)
	cv::Mat kernel(3, 3, CV_32F, cv::Scalar(0));
	// 对内核赋值
	kernel.at<float>(1, 1) = 5.0;
	kernel.at<float>(0, 1) = -1.0;
	kernel.at<float>(2, 1) = -1.0;
	kernel.at<float>(1, 0) = -1.0;
	kernel.at<float>(1, 2) = -1.0;
	// 对图像滤波
	cv::filter2D(image, result, image.depth(), kernel);
}

        在对像素邻域进行计算时,通常用一个核心矩阵来表示。这个核心矩阵展现了如何将与计算 相关的像素组合起来,才能得到预期结果。而本例中的核心矩阵为:

                ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​     

        鉴于滤波是图像处理中的常见操作,OpenCV 专门为此定义了一个函数,即 cv::filter2D。 要使用这个函数,只需要定义一个内核(以矩阵的形式),调用函数并传入图像和内核,即可返 回滤波后的图像。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值