OpenCV重新学习一
遍历图像和领域
本文使用的图像如下:
图像处理中,通过当前位置的相邻像素计算新的像素值是很常见的操作。当邻域包含图像的前几行和下几行时,就需要同时扫描图像的若干行。
锐化(基于laplace算子)一幅图像减去它经过拉普拉斯滤波后的图像,图像边缘将会得到放大,细节部分更加锐利。锐化算子计算如下:
sharpened_pixel = 5*current-left-right-up-down;
图像遍历使用三个指针,一个指向当前行,一个指向上一行,一个下一行。由于每个像素值的计算都需要它的上下左右四个相邻像素,所以不可能对图像第一行,第一列,最后一行和最后一列进行计算。循环体代码如下:
void sharpen(const cv::Mat &image, cv::Mat &result){
result.create(image.size(), image.type());
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 = 1; i < image.cols - 1; i++){
*output++ = cv::saturate_cast<uchar>(5 * current[i] - current[i - 1] - current[i + 1] - 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));
}
模板函数cv::saturate_cast被用来对计算结果进行截断。因为对像素值进行数学计算经常会导致结果超出像素允许范围,解决方法是将值映射到0~255.通常做法将负值截断为0,大于255的值截断为255。此外,输入参数是浮点数的话,会对其取整至最接近输入值的整数。
上述过程实际上是一个图像滤波。由于滤波是常规的图像处理方法,OpenCV定义了一个特殊函数完成滤波处理:cv::filter2D。
void sharpen2D(const cv::Mat &image, cv::Mat &result){
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>(1, 0) = -1.0;
kernel.at<float>(1, 2) = -1.0;
kernel.at<float>(2, 1) = -1.0;
cv::filter2D(image, result, image.depth(), kernel);
}
该实现方式和之前的实现方式输出结果完全相同(效率也完全相同)。但是如果核函数尺寸更大,该方法更高效。
简单的图像算术
图像可以以不同方式组合,因为它们只是一般矩阵,所以可以进行加减乘除运算。OpenCV提供了各种图像算术操作符。
图像相加。通过调用函数cv::add,更准确说是函数cv::addWeighted完成图像加法。
cv::addWeighted(image1, 0.7, image2, 0.9, 0., result);
所有二元算术函数工作方式都是一样的,接受两个输入变量和一个输出变量。某些情况下还需要指定权重为运算中的标量因子。更一般的
//c[i]=a[i]+b[i]
cv::add(imageA, imageB, imageC);
//c[i]=a[i]+k
cv::add(imageA, cv::Scalar(k), imageC);
//c[i]=k1*a[i]+k2*b[i]+k3;
cv::addWeighted(imageA, k1, imageB, k2, k3, imageC);
//c[i]=k*a[i]+b[i]
cv::add(imageA, k, imageB, imageC);
//if(mask[i]) c[i]=a[i]+b[i]
cv::add(imageA, imageB, imageC, mask);
定义感兴趣区域
假设合并两个大小不同的图像。首先定义感兴趣区域(Region of Intrest),关键之处在于ROI和它的父图像指向同一块内存缓冲区。
cv::Mat logo, imageROI, dst_2;
logo = cv::imread("C:\\Users\\Administrator\\Desktop\\logo.bmp");
imageROI = src(cv::Rect(385, 270, logo.cols, logo.rows));
imageROI = src(cv::Range(270, 270 + logo.rows), cv::Range(385, 385 + logo.cols));
方法一:/*cv::addWeighted(imageROI, 1.0, logo, 0.3, 0., imageROI);
cv::imshow("ROI Image", src);*/
方法二:logo.copyTo(imageROI, logo);
cv::imshow("ROI Image", src);
定义ROI的一种方法是使用cv::Rect。指定矩形的左上角坐标和矩形的长宽就可以定义一个矩形区域。
另一种方法是指定感兴趣行或列的范围(Range)。
如果想创建包含原始图像特定行或列的ROI,使用如下代码:
cv::Mat imageROI = src.rowRange(start, end);
cv::Mat imageROI = src.colRange(start, end);