【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】 操作像素 配套的源码下载
相关文章推荐
- OpenCv学习笔记(六)----图像空间缩减,OpenCv中的计时函数和OpenCv中操作图像单个像素点的方法
- 【opencv学习笔记.1】操作像素画圆
- Opencv学习笔记之二:操作像素
- 图像的像素点操作【OpenCV学习笔记3】
- 我的OpenCV学习笔记(三):利用操作像素完成简单的图像处理:加入椒盐噪声、图像翻转、改变对比度、图像锐化
- OpenCV学习笔记 存取像素值操作汇总core
- OpenCV学习笔记二-操作像素
- OpenCV学习笔记(四十一)——再看基础数据结构core OpenCV学习笔记(四十二)——Mat数据操作之普通青年、文艺青年、暴力青年 OpenCV学习笔记(四十三)——存取像素值操作汇总co
- opencv学习笔记 二 操作像素
- openCV学习笔记(四):图像遍历和像素操作
- 【OpenCV学习笔记】三、操作像素
- OpenCV学习笔记——存取像素值操作汇总core
- c# opencvsharp学习笔记(3)兴趣区域ROI,图像叠加,操作像素点
- OpenCV学习笔记二:操作像素
- 我的OpenCV学习笔记(二):操作每个像素
- 【OpenCV学习笔记】一.操作像素
- OpenCV学习笔记(1)——操作像素
- opencv2学习笔记4-操作图像(图像锐化-拉普拉斯算子)
- OpenCV 2 学习笔记(23): 开操作与闭操作
- opencv2学习笔记2-操作图像(椒盐噪声)