1. 用迭代器扫描图像
【准备工作】使用操作图像(上)的减色程序作为例子。
【实现】
#include
#include
#include
#include
#include
using namespace std;
using namespace cv;
void colorReduce(cv::Mat &image, int div = 64) {
//div必须是2的幂
int n = static_cast(
log(static_cast(div)) / log(2.0) + 0.5);
//用来截取像素值的掩码
uchar mask = 0xFF << n;//如果div=16,mask=0xF0
uchar div2 = div >> 1;//div2=div/2
//迭代器
cv::Mat_::iterator it = image.begin();
cv::Mat_::iterator itend = image.end();
//扫描全部像素
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_ it;//或者 cv::Mat_::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_ cimage(image);
cv::Mat_::iterator it= cimage.begin();
cv::Mat_::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
#include
#include
#include
#include
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(j - 1);//上一行
const uchar* current = image.ptr(j); //当前行
const uchar* next = image.ptr(j + 1); //下一行
uchar* output = result.ptr(j);//输出行
for (int i = nchannels; i < (image.cols - 1) * nchannels; i++) {
//应用锐化算子
*output++ = cv::saturate_cast(
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_ it;//或者 cv::Mat_::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(1, 1) = 5.0;
kernel.at(0, 1) = -1.0;
kernel.at(2, 1) = -1.0;
kernel.at(1, 0) = -1.0;
kernel.at(1, 2) = -1.0;
// 对图像滤波
cv::filter2D(image, result, image.depth(), kernel);
}
在对像素邻域进行计算时,通常用一个核心矩阵来表示。这个核心矩阵展现了如何将与计算 相关的像素组合起来,才能得到预期结果。而本例中的核心矩阵为:
鉴于滤波是图像处理中的常见操作,OpenCV 专门为此定义了一个函数,即 cv::filter2D。 要使用这个函数,只需要定义一个内核(以矩阵的形式),调用函数并传入图像和内核,即可返 回滤波后的图像。