您的位置:首页 > 运维架构

【OpenCV学习笔记 005】 操作像素

2016-09-14 21:35 866 查看

一、存取像素值

1.概念及原理

(1)图像像素:一张图像是一个由数值组成的矩阵。这也是Opencv2用cv::Mat这个数据结构来表示图像的原因。矩阵的每一个元素代表一个像素。对于灰度图像而言,像素由8位无符号数来表示,其中0代表黑色,255代表白色。对于彩色图像而言,每个像素需要三个这样的8位无符号数来表示三个颜色通道(红、绿、蓝)。因此,在这种情况下,矩阵的元素是一个三元数。

(2)椒盐噪点:是一种特殊的噪点,顾名思义,它随机将部分像素设置为白色或者黑色。在传输过程中如果部分像素值丢失,那么这种噪点就会出现。

2.实验:

将一张彩色图像随机挑选若干像素,并将其设置为白色.

源码:

#include<iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>

void salt(cv::Mat &image, int n){

for (int k = 0; k < n; k++){

int i = rand() % image.cols;
int j = rand() % image.rows;

if (image.channels()==1)	//灰度图
{
image.at<uchar>(j, i) = 255;
}
else if (image.channels() == 3)	//彩色图
{
image.at<cv::Vec3b>(j, i)[0] = 255;
image.at<cv::Vec3b>(j, i)[1] = 255;
image.at<cv::Vec3b>(j, i)[2] = 255;
}

}
}

void main()
{
//打开图像
cv::Mat image = cv::imread("picture.jpg");
//调用函数增加噪点
salt(image, 3000);
//显示图像
cv::namedWindow("Image");
cv::imshow("Image", image);
cv::waitKey(0);
}
处理后的结果如下图所示:



二、使用指针遍历图像

1.概念及原理

(1)对于三通道的彩色图像来讲,每个通道都是用一个8位的unsigned char表示,及全部可能的颜色数目为256*256*256大约1600万个。为了降低分析的复杂度,将RGB空间划分为同等大小的格子,例如每个维度的颜色数降低为原来的1/8,那么总的颜色数就为32*32*32。原始图像中的每个颜色都替换为它所在格子的中心对应的颜色。

(2)在一个彩色图像,图像数据缓存区中的前三个字节对应图像左上角像素的三个通道值,接下来的三个字节对应第一行的第二个像素,以此类推(注意:OpenCV默认使用BGR的通道顺序,因此第一个通道通常是蓝色)。但是出于效率的考虑,每行会填补一些额外像素,因为如果行的长度为4或8的倍数,一些多媒体处理芯片(如Intel的MMX架构)可以更高效地处理图像。

(3)考虑到效率图像有可能在行尾扩大若干个像素。但是,当不进行填补的时候,图像可以被视为一个长为W*H的一维数组。通过cv::Mat的一个成员函数isContinuous来判断这幅图像是否行进行了填补。我们可以利用图像的连续性,把整个过程使用一个循环完成。

2.实验

将一副图像进行颜色缩减。

源码:

#include<iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>

void colorReduce(cv::Mat&image, int div = 64){

int n = 0;
int n1 = image.rows;	//行数
//每行的元素个数
int nc = image.cols*image.channels();

if (image.isContinuous())
{
//没有额外的填补像素
nc = nc *n1;
n1 = 1;	//it is now a 1D array

}

for (int j = 0; j < n1; j++)
{
//得到第j行的首地址
uchar* data = image.ptr<uchar>(j);
for (int i = 0; i < nc; i++)
{
//颜色缩减方式一:处理每一个像素
//data[i] = data[i] / div * div + div / 2;

//颜色缩减方式二:通过模运算来计算最接近被除数(缩减因子,div)的整数倍数。
//data[i] = data[i] - data[i] % div + div / 2;

//颜色缩减方式三:位运算。位运算非常高效,所以在需要考虑效率的情况下位运算是一个强大的备选方式。
//div = pow(2, n);
//uchar mask = 0xFF << n;//eg for div=16 mask = 0xF0 用来对像素取整的掩模
//data[i] = (data[i] & mask) + div / 2;
}
}

}

void main(){

//装载图像
cv::Mat image = cv::imread("picture.jpg");
//处理图像
colorReduce(image);
//显示图像
cv::namedWindow("Image");
cv::imshow("Image", image);
cv::waitKey(0);
}


处理后的结果如下图所示:



三、使用迭代器遍历图像

在面向对象的编程中,遍历数据集合通常是通过迭代器来完成的,在此我们用迭代器来完成之前的颜色缩减案例。

源码:

#include<iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>

void colorReduceIterator(cv::Mat&image, int div = 64){

//得到初始位置的迭代器
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] = (*it)[0] / div*div + div / 2;
(*it)[1] = (*it)[1] / div*div + div / 2;
(*it)[2] = (*it)[2] / div*div + div / 2;
}
}

void main(){

//装载图像
cv::Mat image = cv::imread("picture.jpg");
//处理图像
colorReduceIterator(image);
//显示图像
cv::namedWindow("Image");
cv::imshow("Image", image);
cv::waitKey(0);
}

如何提升该算法的效率:

(1)对算法重新进行改写

(2)随着多核处理器的普及,OpenMP与Intel Threading Building(TBB)是两个比较流行的并行编程API。

四、遍历图像和邻域操作

1.概念及原理

(1)在图像处理中,通过当前位置的相邻像素计算新的像素是很常见的操作。这个例子基于拉普拉斯算子对图像进行锐化。众所周知,将一副图像减去它经过拉普拉斯滤波后的图像,这幅图像的边缘部分将得到放大,及细节部分更加锐利。这个锐化算子的计算方式:

sharpened_pixel = 5 * current - left - right - up - down;
其中left代表当前像素左边紧挨着它的像素,以此类推。

(2)对于本例中的锐化滤波器,核矩阵为



除非特殊声明,当前像素点对应核矩阵的中心。核的每一项代表对应像素的乘数因子。核在每个像素上的输出等于各个像素与对应因子乘积之和。核的尺寸等于邻域的尺寸(这里是3*3)。将这么一个核应用到图像上就没有这么简单了,这是信号处理中卷积的基础。

源码:

#include<iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv/cv.hpp>

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]);
}
}

//将未处理的像素设置为0
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));
}

//OpenCV定义了一个特殊的函数来完成滤波处理:cv::filter2D 我们利用它来重写图像锐化函数:
void sharpen2D(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);

}

void main(){

//装载图像
cv::Mat image = cv::imread("gray.jpg");
cv::Mat result;
//处理图像
//sharpen(image, result);
sharpen2D(image, result);
//显示图像
cv::namedWindow("Image");
cv::imshow("Image", result);
cv::waitKey(0);
}

处理后的结果如下图所示:



五、进行简单的图像算术

1.概念及原理

(1)图像可以以不同的方式组合,因为它们只是一般的矩阵,所以它们可以做加减乘除运算,opencv提供各种图像算术操作符。

(2)所有的二元算术函数工作方式都是一样的,它接受两个输入变量和一个输出变量。在一些情况下,还需要指定权重作为运算中的标量因子。

2.实验

将以下两幅灰度图像相加。


 


源码:

#include<iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>

void main(){

//装载图像
cv::Mat image1 = cv::imread("gray.jpg");
cv::Mat image2 = cv::imread("rain.jpg");
cv::Mat result;
//处理图像 函数cv::addWeighted来完成图像加法,因为我们需要的是加权和。
cv::addWeighted(image1, 0.7, image2, 0.9, 0., result);

/*
扩展:每种函数都有几个不同的形式,例如cv::add
//c[i] = a[i] + b[i];
cv::add(imageA, imageB, resultC);
//c[i] = a[i] + k;
cv::add(iamgeA, cv::Scalar(k), resultC);
//c[i] = k1*a[i] + k2*b[i] + k3;
cv::addWeighted(iamgeA, k1, imageB, k2, k3, resultC);
//c[i] = k*a[i] + b[i];
cv::scaleAdd(iamgeA, k, imageB, resultC);
*/

//显示图像
cv::namedWindow("Image");
cv::imshow("Image", result);
cv::waitKey(0);
}

处理后的图像:



六、定义感兴趣区域

1.概念及原理

由于cv::add要求两个输入图像具有相同的尺寸。所以我们不能直接使用cv::add,而是需要在使用之前定义感兴趣区域(ROI)。只要感兴趣区域与Logo图像的大小相同,cv::add就能够工作咯!ROI的位置决定了Logo图像被插入的位置。

2.实验

合并两个不同大小的图像

源码

#include<iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>

void main(){

//装载图像
cv::Mat srcImage = cv::imread("lol.jpg");
cv::Mat logoImage = cv::imread("logo.jpg");

//由于logo图像是直接和原始图像相加的,视觉效果不是很令人满意,所以直接将插入的像素设置为logo图像的像素值效果会好一点。
//定义图像ROI
cv::Mat imageROI;
imageROI = srcImage(cv::Rect(320, 210, logoImage.cols, logoImage.rows));
//插入logo
cv::addWeighted(imageROI, 1.0, logoImage, 3.0, 0., imageROI);
//加载掩模(必须是灰度图)
cv::Mat mask = cv::imread("logo.jpg", 0);
//通过掩模拷贝ROI
logoImage.copyTo(imageROI, mask);

/*
另外一种定义ROI的方式是指定感兴趣行或列的范围(Range) cv::Range可以用来定义Range
cv::Mat imageROI = srcImage(cv::Range(320, 320 + logoImage.cols), cv::Range(210, 210 + logoImage.rows));
*/

//显示图像
cv::namedWindow("Image");
cv::imshow("Image", srcImage);
cv::waitKey(0);
}
实验运行结果:



【OpenCV学习笔记 005】 操作像素  配套的源码下载
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: