1、访问像素值方法:直接访问、使用指针、使用迭代器。
2、二值图像中,0代表黑色,1代表白色;灰度图像(8位)0代表黑色,255代表白色。
3、直接访问
Mat类中at方法被实现为一个模板方法,因此调用at时必须指定图像元素类型,eg: image.at<uchar>(j,i) = 255;尖括号内内是模板的参数列表。需要注意指定的类型与矩阵类的元素类型一致,at方法不会进行任何类型转换。对于彩色图像,Mat提供一种向量,将这种短向量 定义为一种类型Vec3b,使用下标运算符依次访问向量中的三个元素,访问方法如下:
image.at<Vec3b>(j,i)[channel] = value; //channel是通道序号0,1,2
image.at<Vec3b>(j,i) = Vec3b(255,255,255);
修改图像的函数使用图像作为参数,使用了值传递,是因为他们在复制图像的时候共享了图像头,不需要使用引用传递。同时通过这种访问方式就可以访问像素。
4、减色算法
void colotReduce(Mat image, int diw = 64); //函数签名
void colotReduce(Mat image, int diw = 64)
{
int ni = image.rows; //行数
int nc = image.cols * image.channels(); //每行中的元素
for(int j = 0; j < ni; j++)
{
uchar* data = image.ptr<uchar>(j); ptr模板函数返回第j行的首元素地址
for(int i = 0; i < nc; i++)
{
data[i] = data[i]/div * div + div/2;
//*data++ = *data/div * div + div/2; //这是用指针递增来遍历一行中的元素
}
}
}
减色算法的实现除了上述的整数除法性质,也可以用取模运算data[i] - data[i]%div + div/2;还可以使用位运算符 ,但是没看懂。。。。(位运算是效率最高的)
5、用指针访问
4中的减色函数直接在输入图像中进行了转换,称为就地转换,但有时希望保护原图像,可以考虑克隆一个副本,对副本操作,但可以构造一种方法,实现将这两种方法综合。使用下述函数,当选择就地转换时,可以让输入输出为同一图像,不用再函数外创建result对象,如果不就地转换,就需要创建而且输入输出不同。
void colorReduce(const Mat& image, Mat& result, int div = 64);
//如果采取就地处理,则image与result为同一值,否则在函数外new一个result
Mat result;
void colorReduce(const Mat& image, Mat& result, int div = 64);
{
int ni = image.rows; //行数
int nc = image.cols * image.channels(); //每行中的元素
result.creat(image.rows, image.cols, image.type();
for(int j = 0;j < ni; j++)
{
const uchar* data_in = image.ptr<uchar>(j);//全过程只使用输入图像并未改变图像
uchar* data_out = result.ptr<uchar>(j);
for(int i = 0;i < nc; i++)
{
data_out[i] = data_in[i]/div*div + div/2;
}
}
}
6、对连续图像的高速扫描
为了提高性能,会添加像素,成8的倍数。去掉填充后的图像可以看做是一个长一维数组,行与行之间是连续的,此时认为该图像具有连续性,我们可以用isContinuous方法检测图像是否有连续性,如有连续性,便可以将20×20的二维图像转换为1×40的 一维图像
int ni = image.rows;
int nc = image.cols * image.channels();
if(iamge.isContimuous())
{
nc = nc * ni;
ni = 1;
}
7、使用迭代器访问像素
void colotReduce(Mat image, int diw = 64)
{
/*MatIterator_<Ved3b> it;//这两种方法都可以创建迭代器对象
cv::Mat_<Vec3b>::iterator it;*/
MatIterator_<Ved3b> it = image.begin<Vec3b>();//注意模板参数的位置,括号前,函数名后
MatIterator_<Ved3b> itend = image.end<Vec3b>();
for(;it != itend; it++)
{
(*it)[0] = (*it)[0]/div * div + div/2;
(*it)[1] = (*it)[0]/div * div + div/2;
(*it)[2] = (*it)[0]/div * div + div/2;
//it->channel[0] = (it->channel[0]) /div * div + div/2;//这种形式也可以
}
}
使用迭代器看起来更简洁,还可以申明常量迭代器,只需替换为,MatConstIterator,以及const_iterator,还可以用Mat_实例的引用来获取迭代器位置。
8、各种方法特点
直接访问法:用时很长,适合随机访问像素,不适合扫描
指针访问:用时还好,没啥特点
迭代器访问:看起来直接明了,用时比较长
减少循环里的计算量,头线程,使用较短的语句都可以提高效率。
9、访问相邻像素
使用核心矩阵,锐化核,是滤波的一种。构造内核,然后调用filter2D函数。使用at方法访问内核的像素。saturate_cast方法用来防止溢出。使用该方法可以锐化图像。
10、简单图像运算
一般是对图像相加或者加权,可以使用add函数。简单图像运算的用途是创建特效图或者覆盖图像中的信息。
//这是常用的四种模式
//c[i]= a[i]+b[i] ;
cv: :add (imageA, imageB, resultC);
//c[i]= a[i]+k;
cv: :add (imageA, cv: :Scalar (k) , resultC) ;
//c[i]= k1*a[i] +k2*b[i]+k3;
cv: :addWeighted ( imageA,k1, imageB, k2,k3, resultC) ;
//c[i]= k*a[i] +b[i] ;
cv: :scaleAdd ( imageA, k, imageB, resultC) ;
也可以使用addWeighted(image1, 0.7, image2, 0.9, 0.0, result),类似add中的第三种。
11、通道分割
使用split(image1,planees)方法可以将image1这个完整的图像的三个通道分别复制到三个Mat类实例当中;使用mergc方法可以完成通道分离函数的逆过程。
vector<Mat> planes; //这是三幅图像的向量
split(image1,planes); //分离
merge(planes,result); //组合
12、图像重映射
是指利用特定的映射关系来修改像素的位置,可以实现波浪、倾斜等特效。
void wave(const Mat& image; Mat result)
{
Mat scrX(image.rows, image.cols, CV_32F);//用来存储原图像中每个像素点的x轴坐标
Mat scrX(image.rows, image.cols, CV_32F);//用来存储原图像中每个像素点的y轴坐标
for(int i = 0; i < image.rows; i++) //通过循环装定各轴的像素点的位置
{
for(int j = 0; j < image.rows; j++)
{
srcX.at<float>(i,j) = j; //at函数是用来获取指定像素位置的Mat数据
srcY.at<float>(i,j) = i + 5*sin(j/10.0);
}
}
remap(image, scrX, scrY, INTER_LINEAR);
}
上图是重映射的流程