1、实际中的使用案例
void Threshold(IplImage *Image, IplImage *Image_O)
{
int thresMax = 0, thresMin = 255,;
int i = 0, j = 0, t = 0;
for (j = 0; j < Image->height; j++)//图像高度方向
for (i = 0; i < Image->width; i++)//图像宽度方向
{
if (CV_IMAGE_ELEM(Image, uchar, j, i) > thresMax)
thresMax = CV_IMAGE_ELEM(Image, uchar, j, i); //获取图像中最大像素值
else if (CV_IMAGE_ELEM(Image, uchar, j, i) < thresMin)
thresMin = CV_IMAGE_ELEM(Image, uchar, j, i); //获取图像中最小像素值
}
}
2、详细参数解释:
CV_IMAGE_ELEM 是一个宏函数,基本形式:
/** 对于多通道图像(col),在对(col,row)位置像素的引用中,应乘以通道数*/
#define CV_IMAGE_ELEM( image, elemtype, row, col ) \
(((elemtype*)((image)->imageData + (image)->widthStep*(row)))[(col)])
CV_IMAGE_ELEM(image,elemtype,row,col)
变量 | 解释 | 注意 |
image | image为指针数组 | 指针数组 |
elemtype | elemtype为数据的存取类型 | 数据类型 |
row | row为图像的高度,范围:0~height | row 和col顺序不能混淆 |
col | col为图像的宽度,范围:0~width | row 和col顺序不能混淆 |
一句话来解释就是:读取图像中坐标为(col,row)的像素的数据信息。
3、读取图像中坐标位置数据信息的其他方式
3.1 Mat.at<float>(i,j)
/*Mat.at<float>(i,j)法*/
Mat inverseColor1(Mat srcImg)
{
Mat temp = srcImg.clone();//克隆图像
int row = temp.rows;//获取图像的总行数
int col = temp.cols;//获取图像的总列数
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col;j++)
{
//temp.at<cv::Vec3b>(i, j)[0] 获取在第(i行, j列)位置的像素值
temp.at<cv::Vec3b>(i, j)[0] = 255 - temp.at<cv::Vec3b>(i, j)[0];//R通道反色
temp.at<cv::Vec3b>(i, j)[1] = 255 - temp.at<cv::Vec3b>(i, j)[1];//G通道反色
temp.at<cv::Vec3b>(i, j)[2] = 255 - temp.at<cv::Vec3b>(i, j)[2];//B通道反色
}
}
return temp;
}
temp.at<cv::Vec3b>(i, j)[n]表示彩色3通道图像中i行j列第k个通道的颜色像素值,其中<cv::Vec3b>是opencv里面的像素值类型。
其函数模板为typedef Vec<uchar,3>Vec3b,表示3通道uchar。
注意:Mat.at虽然实现比较简单,但是在速度上比较慢。
3.2 指针遍历Mat::ptr<type>
/*指针遍历Mat::ptr<type>*/
Mat inverseColor2(Mat srcImg)
{
Mat temp = srcImg.clone();
int row = temp.rows;
int col = temp.cols;
int nStep = temp.cols * temp.channels();//总列数据=图像总列数 * 通道数
for (int i = 0; i < row; i++)
{
uchar *pSrcData = srcImg.ptr<uchar>(i);//一次获取第i行的数据,存放在uchar数组中
uchar *pResuiltData = temp.ptr<uchar>(i);//一次获取第i行的数据,存放在uchar数组中
for (int j = 0; j < nStep;j++)
{
//saturate_cast<uchar> 防呆机制,例如小于0的数据会调整为0,大于255的数据会调整为255
pResuiltData[j] = saturate_cast<uchar>(255 - pSrcData[j]);
}
}
return temp;
}
Opencv提供了ptr的方法,用于表示遍历图像的每一个字节,默认返回值为uchar* 或者const uchar* 。
来自模板temp<typename _Tp>_Tp*Mat::ptr(int i0=0),i0代表是以零行为基准的索引,函数返回指向特定矩阵的uchar* 或指针。
ptr速度优于at和MatIterator_,是最常用的一种方式。
相比Mat.at方法而言,操作方式复杂,运用到指针操作。
MatConstIterator_和MatIterator_是两个迭代器;
MatConstiterator 将迭代器设置为构造函数。
MatConstIterator_和MatIterator_ 两者是将迭代器设置为矩阵指定元素的构造函数。
迭代器MatIterator_速度是五种方式里面的最慢的,操作方式比较复杂。
3.3 迭代器MatIterator_
/* 迭代器MatIterator_ */
Mat inverseColor3(Mat srcImg)
{
Mat temp = srcImg.clone();
MatConstIterator_<Vec3b> srcIterStart = srcImg.begin<Vec3b>();//表示指向迭代器的起始位置。
MatConstIterator_<Vec3b> srcIterEnd = srcImg.end<Vec3b>();//表示指向迭代器的终止位置
MatIterator_<Vec3b> resIterStart = temp.begin<Vec3b>();//表示指向迭代器的起始位置。
MatIterator_<Vec3b> resIterEnd = temp.end<Vec3b>();//表示指向迭代器的终止位置
//通过起始起始和终止位置之间,移动指针完成对图像的像素遍历。
while (srcIterStart != srcIterEnd)
{
(*resIterStart)[0] = 255 - (*srcIterStart)[0];//R
(*resIterStart)[1] = 255 - (*srcIterStart)[1];//G
(*resIterStart)[2] = 255 - (*srcIterStart)[2];//B
srcIterStart++;
resIterStart++;
}
return temp;
}
3.4 连续操作法 isContinouous
/* 连续操作法 isContinouous */
Mat inverseColor4(Mat src)
{
int row = src.rows;
int col = src.cols;
cv::Mat temp = src.clone();
// 判断是否是连续图像,即是否有像素填充
if (src.isContinuous() && temp.isContinuous())
{
row = 1;
// 按照行展开
col = col * src.rows * src.channels();
}
// 遍历图像的每个像素
for (int i = 0; i < row; i++)
{
// 设定图像数据源指针及输出图像数据指针
const uchar* pSrcData = src.ptr<uchar>(i);
uchar* pResultData = temp.ptr<uchar>(i);
for (int j = 0; j < col; j++)
{
*pResultData++ = 255 - *pSrcData++;
}
}
return temp;
}
用Mat存储一幅图像时,若图像在内存中是连续存储的(Mat对象的isContinuous == true),则可以将图像的数据看成是一个一维数组,而data(uchar*)成员就是指向图像数据的第一个字节的,因此可以用data指针访问图像的数据,从而加速Mat图像的访问速度。
但是往往图像行与行之间的存储可能是不连续的,进行像素值遍历,很大程度上造成数据指针移动的浪费。Mat提供了一个检测是否连续的函数isContinuous(),1xN的图像矩阵是连续的。当图像元素连续时,可以看成一行,按行展开,利用指针来获取起始行的位置,进行遍历,
节省了寻址的时间。
例:一张M x N的图像按行展开后,成为了1 x( N x M) 列的连续像素点。isContinuous()的方法操作难度居中,推荐使用(备注:图像必须是连续的才能使用)。
注意:一般经过裁剪的Mat图像,都不再连续了,如cv::Mat crop_img = src(rect);crop_img 是不连续的Mat图像,如果想转为连续的,最简单的方法,就是将不连续的crop_img 重新clone()一份给新的Mat就是连续了。
3.5 LTU查表法
/* LTU查表法 */
Mat inverseColor5(Mat src)
{
int row = src.rows;
int col = src.cols;
Mat temp = src.clone();
uchar LutTable[256 * 3]; // 建立LUT 反色table
//初始化
for (int i = 0; i < 256; ++i)
{
LutTable[i * 3] = 255 - i;
LutTable[i * 3+1] = 255 - i;
LutTable[i * 3+2] = 255 - i;
}
Mat lookUpTable(1, 256, CV_8UC3, LutTable);
// 应用索引表进行查找
LUT(src, lookUpTable, temp);
return temp;
}
/*LTU查表反色处理(单通道)*/
Mat inverseColor5_1(Mat src)
{
int row = src.rows;
int col = src.cols;
Mat temp = src.clone();
uchar LutTable[256]; // 建立LUT 反色table
for (int i = 0; i < 256; ++i)
{
LutTable[i] = 255 - i;
}
Mat lookUpTable(1, 256, CV_8U);
uchar* pData = lookUpTable.data;
// 建立映射表
for (int i = 0; i < 256; ++i)
{
pData[i] = LutTable[i];
}
// 应用索引表进行查找
LUT(src, lookUpTable, temp);
return temp;
}
LUT函数作用:数组的查找表转换。函数LUT以来自查找表的值填充输出数组。每个像素的索引是从输入数组中获取的,此函数处理src的每个元素逻辑如下:
dst(I)←lut(src(I) + d)
参数解释:src输入阵列的8位元素。256个元素的lut查找表;
在多通道输入阵列的情况下,该表应该有一个单独的通道(在本例中为所有通道使用相同的表)或与输入阵列相同的通道数量。与src相同大小和通道数量的dst输出阵列,和lut的深度相同。总之,单通道使用单个映射表;多通道输入时,可以多个通道使用同一个映射表,也可以使用多个映射表(看个人需求),上例三通道图像使用同一个表,输出图像的通道和映射表的深度有关。
4 如何实现一个alpha混合(透明混合)函数
/*如何实现一个alpha混合(透明混合)函数*/
template<typename T>
void alphaBlendRGBA(const Mat& src1, const Mat& src2, Mat& dst)
{
const float alpha_scale = (float)std::numeric_limits<T>::max(),
inv_scale = 1.f/alpha_scale;
CV_Assert( src1.type() == src2.type() &&
src1.type() == CV_MAKETYPE(DataType<T>::depth, 4) &&
src1.size() == src2.size());
Size size = src1.size();
dst.create(size, src1.type());
// here is the idiom: check the arrays for continuity and,
// if this is the case,
// treat the arrays as 1D vectors
if( src1.isContinuous() && src2.isContinuous() && dst.isContinuous() )
{
size.width *= size.height;
size.height = 1;
}
size.width *= 4;
for( int i = 0; i < size.height; i++ )
{
// when the arrays are continuous,
// the outer loop is executed only once
const T* ptr1 = src1.ptr<T>(i);
const T* ptr2 = src2.ptr<T>(i);
T* dptr = dst.ptr<T>(i);
for( int j = 0; j < size.width; j += 4 )
{
float alpha = ptr1[j+3]*inv_scale, beta = ptr2[j+3]*inv_scale;
dptr[j] = saturate_cast<T>(ptr1[j]*alpha + ptr2[j]*beta);
dptr[j+1] = saturate_cast<T>(ptr1[j+1]*alpha + ptr2[j+1]*beta);
dptr[j+2] = saturate_cast<T>(ptr1[j+2]*alpha + ptr2[j+2]*beta);
dptr[j+3] = saturate_cast<T>((1 - (1-alpha)*(1-beta))*alpha_scale);
}
}
}