题外话:像素大战,感兴趣的可以去看这个电影,就能更好的理解像素是个什么东东。
像素(pixel):由Picture图像和Element元素组成,是图片视频的基本单元。也就是我们通常说的分辨率。当图片尺寸以像素为单位时,每一厘米等于28像素,比如15*15厘米的图片,等于420*420像素的长度。
在 opencv 中经常出现通道,所以在单通道、多通道下的图像的像素是这样表示的图画的不好看,明天补上!
1、存取像素at(int x,int y);
要指定元素所在的行和列。若图像是单通道,返回值是单个数值,若图像是多通道,返回值是一组向量(vector)。
cv::Mat的公有成员变量cols和rows给出了图像的宽和高
at()函数的使用,有一点很重要,在编译时要知道图像的数据类型。
单通道:
image.at<uchar>(j,i)=value;
多通道:
image.at<Vec3b>(j,i)[0] =value;
image.at<Vec3b>( j,i)[1] =value;
image.at<Vec3b>( j,i)[2] =value;
at中(i,j)的顺序表示的是第i行第j列,跟Point(i,j)和Rect(i,j)中表示第j行第i列是相反的,所以上面都写为at(j,i).这一点不注意非常容易出现内存异常的错误。
图像遍历是图像处理中少不了的,一般有指针和迭代器两种方式。
2、指针遍历
其只能针对没有经过填充的连续图像。判断方法调用isContinuous,返回值为TRUE或false
3迭代器
在面向对象编程中,遍历数据集合通常都是通过迭代器完成的。迭代器是一种特殊的类,专门用来遍历集合中的各个元素,同时隐藏在给定的集合上元素迭代的具体实现方式。
/*
*本程序是学会使用:1、用下标访问像素,2、使用迭代器访问像素 , 3、使用指针访问像素
*4、把图像写入磁盘
*
*
*/
#include <opencv2/highgui/highgui.hpp>
//访问元素用下标
/*这个函数每次循环都将一个随机选取的像素的值设置为255,随机选取的像素的行号i和列j 是通过随机函数得到的。
通过检查图像的通道数来区分灰度和彩色图,对于灰度图直接将像素值设置为255,对于彩色图,需要将每一个通道值
都设置为255才能得到一个白色像素。
*/
void salt(cv::Mat &img , int n)
{
for(int k = 0 ; k < n ; ++ k)
{
int i = rand() % img.cols ;
int j = rand() % img.rows ;
//灰度图
if(1 == img.channels())
{
img.at<uchar>(j , i) = 255 ;
}
//彩色图
else if(3 == img.channels())
{
img.at<cv::Vec3b>(j , i)[0] = 255 ;
img.at<cv::Vec3b>(j , i)[1] = 255 ;
img.at<cv::Vec3b>(j , i)[2] = 255 ;
}
}
//扩展:
//cv::Mat_<uchar> im1 = img ;
//im1(50 , 100) = 0 ;//对于已经知道返回类型时 可以这样访问元素
}
//减少图像颜色数
void colorReduce(cv::Mat &img , int div = 64)
{
//行数
int rowsNum = img.rows ;
//每行的元素个数
int closPiexlNum = img.cols * img.channels() ;
for(int i = 0 ; i < rowsNum ; ++ i)
{
//得到j行首地址
uchar *data = img.ptr<uchar>(i) ;
for(int j = 0 ; j < closPiexlNum ; ++ j)
{
//处理每个像素
data[j] = data[j] / div * div + div / 2 ;
}
}
}
void colorReduce3(cv::Mat img , cv::Mat &result , int div = 64)
{
result.create(img.rows , img.cols , img.type()) ;
int rowsNum = img.rows ;
int closPiexlNum = img.cols * img.channels() ;
for(int i = 0 ; i < rowsNum ; ++ i)
{
uchar *data_in = img.ptr<uchar>(i) ;
uchar *data_out = result.ptr<uchar>(i) ;
for(int j = 0 ; j < closPiexlNum ; ++ j)
{
data_out[j] = data_in[j] / div * div + div / 2 ;
}
}
}
void colorReduce4(cv::Mat img , cv::Mat &result , int div = 64)
{
//result.create(img.rows , img.cols , img.type()) ;
result = img.clone() ;
//注意两种迭代器的定义方法
cv::MatIterator_<cv::Vec3b> itStart = result.begin<cv::Vec3b>() ;
cv::Mat_<cv::Vec3b>::iterator itEnd = result.end<cv::Vec3b>() ;
while(itStart != itEnd)
{
(*itStart)[0] = (*itStart)[0] / div * div + div / 2 ;
(*itStart)[1] = (*itStart)[1] / div * div + div / 2 ;
(*itStart)[2] = (*itStart)[2] / div * div + div / 2 ;
itStart ++ ;
}
//Const迭代器两种定义方法
//cv::MatConstIterator_<cv::Vec3b> it ;
//cv::Mat_<cv::Vec3b>::const_iterator it ;
}
void colorReduce5(cv::Mat img , cv::Mat &result , int div = 64)
{
result = img.clone() ;
int rNum = result.rows ;
int cNum = result.cols ;
if(result.isContinuous())
{
cNum *= rNum ;
rNum = 1 ;
}
int n = static_cast<int>(log(static_cast<double>(div)) / log(2.0)) ;
uchar mask = 0xFF << n ;
for(int i = 0 ; i < rNum ; ++ i)
{
uchar *data = result.ptr<uchar>(i) ;
for(int j = 0 ; j < cNum ; ++ j)
{
*data++ = *data & mask + div / 2 ;
*data++ = *data & mask + div / 2 ;
*data++ = *data & mask + div / 2 ;
}
}
}
void colorReduce6(cv::Mat img , cv::Mat &result , int div = 64)
{
int n = static_cast<int>(log(static_cast<double>(div)) / log(2.0)) ;
uchar mask = 0xFF << n ;
result = (img & cv::Scalar(mask , mask , mask)
+ cv::Scalar(div / 2 , div / 2 , div / 2)) ;
}
int main()
{
cv::Mat img ;
img = cv::imread("E://Baboon.jpg") ;
cv::namedWindow("Original") ;
cv::imshow("Original" , img) ;
//salt(img , 3000) ;
//colorReduce(img , 64) ;
//cv::Mat imgClone = img.clone() ;
cv::Mat result ;
//colorReduce3(img , result) ;//使用指针
//colorReduce4(img , result) ;//使用迭代器
//colorReduce5(img , result) ;//使用位运算
colorReduce6(img , result) ;//简洁的写法
cv::namedWindow("Result") ;
cv::imshow("Result" , result) ;
//cv::imwrite("result.jpg" , img) ;//把图像写在磁盘上
cv::waitKey() ;
return 0 ;
}