2018-11-30 12:34:25 qq_27278957 阅读数 1704

简述

我们在图像处理时经常会用到遍历图像像素点的方式,同样是遍历图像像素点,共有很多中方法可以做到;在这些方法中,有相对高效的,也有低效的;不是说低效的方法就不好,不同场景使用不同方法。

方法

下面将一一介绍这些遍历图像像素点的方法:

方法一:数组遍历法1

图像Mat中每个像素点,其实就是一个值(int、float、double、uchar等类型),而Mat是一个二维数组。

1、单通道图像(CV_8UC1);backImg是单通道灰色图。

int height = backImg.rows;
int width = backImg.cols;
for (int i = 0; i < height; i++)
{
	for (int j = 0; j < width; j++)
	{
		int index = i * width + j;
		//像素值
		int data = (int)backImg.data[index];
	}
}

2、三通道图像(CV_8UC3);backImg、colorImg均为三通道彩色图,且大小相同。

int height = backImg.rows;
int width = backImg.cols;

//差值图
//三个通道中只要有一个差值大于阈值,就进行提取
Mat subImg(height, width, CV_8UC1);
subImg.setTo(0);
for (int i = 0; i < height; i++)
{
	for (int j = 0; j < width; j++)
	{
		int index = i * width + j;
		//背景
		int b1 = (int)backImg.data[3 * index + 0];
		int g1 = (int)backImg.data[3 * index + 1];
		int r1 = (int)backImg.data[3 * index + 2];
		//当前图
		int b2 = (int)colorImg.data[3 * index + 0];
		int g2 = (int)colorImg.data[3 * index + 1];
		int r2 = (int)colorImg.data[3 * index + 2];
		
		//th为阈值
		if (g1 - g2 > th || b1 - b2 > th || r1 - r2 > th)
		{
			subImg.data[index] = 255;
		}
	}
}

方法二:数组遍历法2 -- at<typename>(i,j)

Mat类提供了一个at的方法用于取得图像上的点,它是一个模板函数,可以取到任何类型的图像上的点。

void colorReduce(Mat& image)
{
     for(int i=0;i<image.rows;i++)
     {
		 for(int j=0;j<image.cols;j++)
		 {
			if(image.channels() == 1)
			{
				//取出灰度图像中i行j列的点
				image.at<uchar>(i,j) = image.at<uchar>(i,j) / 2;			
			}
			else if(image.channels() == 3)
			{
				//取出彩色图像中i行j列第k(0/1/2)通道的颜色点
				image.at<Vec3b>(i,j)[0]=image.at<Vec3b>(i,j)[0] / 2;
				image.at<Vec3b>(i,j)[1]=image.at<Vec3b>(i,j)[1] / 2;
				image.at<Vec3b>(i,j)[2]=image.at<Vec3b>(i,j)[2] / 2;
			}			 
		 }
	 }
}    

方法三:指针遍历法

1、这种图像遍历方法相比“数组遍历法”更高效

 void colorReduce(Mat& srcImg,Mat& dstImg)
{
	dstImg = srcImg.clone();
	dstImg.setTo(0);
	int height = srcImg.rows;
	// 将3通道转换为1通道
	int width = srcImg.cols * srcImg.channels();
	for(int k = 0;k < height; k++)
	{
		// 获取第k行的首地址
		const uchar* inData = srcImg.ptr<uchar>(k);
		uchar* outData = dstImg.ptr<uchar>(k);
		//处理每个像素
		for(int i = 0; i < width; i++)
		{
			outData[i] = inData[i] / 2;
		}
	}
}   

程序中将三通道的数据转换为1通道,在建立在每一行数据元素之间在内存里是连续存储的,每个像素三通道像素按顺序存储。
但是这种用法不能用在行与行之间,因为图像在opencv里的存储机制问题,行与行之间可能有空白单元。这些空白单元对图像来说是没有意思的,只是为了在某些架构上能够更有效率,比如intel MMX可以更有效的处理那种个数是4或8倍数的行。

2、但是我们可以申明一个连续的空间来存储图像,这样效率就会更高。图像可以是连续的,也可以是不连续的,Mat提供了一个检测图像是否连续的函数isContinuous(),当图像连通时,我们就可以把图像完全展开,看成是一行。

void colorReduce(Mat& srcImg,Mat& dstImg)
{
	int height = srcImg.rows;
	int width = srcImg.cols;
	dstImg = srcImg.clone();
	dstImg.setTo(0);
	//判断图像是否连续
	if(srcImg.isContinuous() && dstImg.isContinuous())
	{
		height = 1;
		width = width * srcImg.rows * srcImg.channels();
	}
	for(int i = 0; i< height; ++i)
	{
		const uchar* inData = srcImg.ptr<uchar>(i);
		uchar* outData = dstImg.ptr<uchar>(i);
		for(int j = 0; j< width; ++j)
		{
		   outData[j] = inData[j] / 2;
		}
	}
}

方法四:迭代器遍历法

1、迭代器Matlterator_

Matlterator_是Mat数据操作的迭代器,:begin()表示指向Mat数据的起始迭代器,:end()表示指向Mat数据的终止迭代器。迭代器方法是一种更安全的用来遍历图像的方式,首先获取到数据图像的矩阵起始,再通过递增迭代实现移动数据指针。

//三通道彩色图
void colorReduce(Mat srcImage)
{
	Mat tempImage = srcImage.clone();  
  
    // 初始化原图像迭代器  
    MatConstIterator_<Vec3b> srcIterStart = srcImage.begin<Vec3b>();  
    MatConstIterator_<Vec3b> srcIterEnd = srcImage.end<Vec3b>();  
  
    // 初始化输出图像迭代器  
    MatIterator_<Vec3b> resIterStart = tempImage.begin<Vec3b>();  
    MatIterator_<Vec3b> resIterEnd = tempImage.end<Vec3b>();  
  
    while (srcIterStart != srcIterEnd)   
    {  
        (*resIterStart)[0] = 255 - (*srcIterStart)[0];  
        (*resIterStart)[1] = 255 - (*srcIterStart)[1];  
        (*resIterStart)[2] = 255 - (*srcIterStart)[2];  
  
        srcIterStart++;  
        resIterStart++;  
    } 
}
//单通道灰色图
void colorReduce(Mat srcImage)
{
	Mat tempImage = srcImage.clone();  
  
    // 初始化原图像迭代器  
    MatConstIterator_<uchar> srcIterStart = srcImage.begin<uchar>();  
    MatConstIterator_<uchar> srcIterEnd = srcImage.end<uchar>();  
  
    // 初始化输出图像迭代器  
    MatIterator_<uchar> resIterStart = tempImage.begin<uchar>();  
    MatIterator_<uchar> resIterEnd = tempImage.end<uchar>();  
  
    while (srcIterStart != srcIterEnd)   
    {  
        *resIterStart = 255 - *srcIterStart;    
        srcIterStart++;  
        resIterStart++;  
    } 
}

2、迭代器Mat_

OpenCV定义了一个Mat的模板子类为Mat_,它重载了operator()让我们可以更方便的取图像上的点。

void colorReduce(Mat &img)
{
	uchar t;
	//单通道图像
	if(img.channels() == 1)
	{
		Mat_<uchar>::iterator it = img.begin<uchar>();
		Mat_<uchar>::iterator itend = img.end<uchar>();
		while(it != itend)
		{
			//相关操作
			*it = 0;
			it++;
		}
	}
	//三通道图像
	else if(img.channels() == 3)
	{
		Mat_<Vec3b>::iterator it = img.begin<Vec3b>();
		Mat_<Vec3b>::iterator itend = img.end<Vec3b>();
		while(it != itend)
		{
			//相关操作
			(*it)[0] = 0;
			(*it)[1] = 1;
			(*it)[2] = 2;
			it++;
		}
	}
}
//单通道图像(CV_8UC1)src是单通道灰色图
//作用:统计图像中白色像素个数
int BaseFun::whiteSums(Mat src)	
{	
	int counter = 0;
	//迭代器访问像素点
	Mat_<uchar>::iterator it = src.begin<uchar>();
	Mat_<uchar>::iterator itend = src.end<uchar>();  
	for (; it!=itend; ++it)
	{
		if((*it)>0) counter+=1;//二值化后,像素点是0或者255
	}			
	return counter;
}

方法五:Mat_数组遍历法(非迭代器法,Mat_迭代器法已在方法四中介绍)

Mat_也可以通过下标的方式来遍历图像,但是相比Mat::at()方法,无疑Mat_通过下标遍历图像是慢的,不推荐这种方式,如果要使用Mat_,最好只是使用迭代器Mat_<>::iterator。

//单通道图像
void colorReduce(Mat srcImg)
{
	int height = srcImg.rows;
	int width = srcImg.cols;
	//转化为Mat_<uchar>
	Mat_<uchar> tempImg = (Mat_<uchar>&)srcImg;

	for (int i = 0; i < height; i++)
	{
		for (int j = 0; j < width; j++)
		{
			tempImg(i,j) = tempImg(i,j) * 2;
			printf("%d ",tempImg(i,j));	
		}
		printf("\n");
	}
}
//三通道图像	
void colorReduce(Mat srcImg)
{		
	Mat_<Vec3b> tempImg = (Mat_<Vec3b>&)srcImg;
	int height = srcImg.rows;
	int width = srcImg.cols;
	for (int i = 0; i < height; i++)
	{
		for (int j = 0; j < width; j++)
		{
			printf("%d ",tempImg(i,j)[0]);	
			printf("%d ",tempImg(i,j)[1]);	
			printf("%d ",tempImg(i,j)[2]);	
		}
		printf("\n");
	}
}

 Mat_<uchar>对应的是CV_8U,Mat_<char>对应的是CV_8S,Mat_<int>对应的是CV_32S,Mat_<float>对应的是CV_32F,Mat_<double>对应的是CV_64F,对应的数据深度如下:

• CV_8U - 8-bit unsigned integers ( 0..255 )

• CV_8S - 8-bit signed integers ( -128..127 )

• CV_16U - 16-bit unsigned integers ( 0..65535 )

• CV_16S - 16-bit signed integers ( -32768..32767 )

• CV_32S - 32-bit signed integers ( -2147483648..2147483647 )

• CV_32F - 32-bit floating-point numbers ( -FLT_MAX..FLT_MAX, INF, NAN )

• CV_64F - 64-bit floating-point numbers ( -DBL_MAX..DBL_MAX, INF, NAN )

附一张opencv官方给出的关于Mat_解释的图片:

总结

目前找到了这些方式来遍历图像像素,全部亲测可用。如有朋友发现新的遍历方法,可一起交流。

本文部分内容参考博客:http://www.cnblogs.com/ronny/p/opencv_road_2.html,感谢这位博主。

2015-10-20 20:14:06 gdut2015go 阅读数 2458
opencv2-遍历图像像素的14种方法

//---------------------------------【宏定义部分】---------------------------------------------
// 描述:包含程序所使用宏定义
//-------------------------------------------------------------------------------------------------
#define NTESTS 14

#define NITERATIONS 20

//----------------------------------------- 【方法一】-------------------------------------------
// 说明:利用.ptr 和 []
//-------------------------------------------------------------------------------------------------
void colorReduce0(Mat &image, int div=64) {
int nl= image.rows; //行数
int nc= image.cols * image.channels(); //每行元素的总元素数量
for (int j=0; j<nl; j++) 
{
uchar* data= image.ptr<uchar>(j);
for (int i=0; i<nc; i++) 
{
//-------------开始处理每个像素-------------------
data[i]= data[i]/div*div + div/2;
//-------------结束像素处理------------------------
} //单行处理结束                  
}
}
//-----------------------------------【方法二】-------------------------------------------------
// 说明:利用 .ptr 和 * ++ 
//-------------------------------------------------------------------------------------------------
void colorReduce1(Mat &image, int div=64) {
int nl= image.rows; //行数
int nc= image.cols * image.channels(); //每行元素的总元素数量
for (int j=0; j<nl; j++) 
{
uchar* data= image.ptr<uchar>(j);
for (int i=0; i<nc; i++) 
{
//-------------开始处理每个像素-------------------
*data++= *data/div*div + div/2;
//-------------结束像素处理------------------------
} //单行处理结束              
}
}
//-----------------------------------------【方法三】-------------------------------------------
// 说明:利用.ptr 和 * ++ 以及模操作
//-------------------------------------------------------------------------------------------------
void colorReduce2(Mat &image, int div=64) {
int nl= image.rows; //行数
int nc= image.cols * image.channels(); //每行元素的总元素数量
for (int j=0; j<nl; j++) 
{
uchar* data= image.ptr<uchar>(j);
for (int i=0; i<nc; i++) 
{
//-------------开始处理每个像素-------------------
int v= *data;
*data++= v - v%div + div/2;
//-------------结束像素处理------------------------
} //单行处理结束                   
}
}
//----------------------------------------【方法四】---------------------------------------------
// 说明:利用.ptr 和 * ++ 以及位操作
//----------------------------------------------------------------------------------------------------
void colorReduce3(Mat &image, int div=64) {
int nl= image.rows; //行数
int nc= image.cols * image.channels(); //每行元素的总元素数量
int n= static_cast<int>(log(static_cast<double>(div))/log(2.0));
//掩码值
uchar mask= 0xFF<<n; // e.g. 对于 div=16, mask= 0xF0
for (int j=0; j<nl; j++) {
uchar* data= image.ptr<uchar>(j);
for (int i=0; i<nc; i++) {
//------------开始处理每个像素-------------------
*data++= *data&mask + div/2;
//-------------结束像素处理------------------------
}  //单行处理结束            
}
}
//----------------------------------------【方法五】----------------------------------------------
// 说明:利用指针算术运算
//---------------------------------------------------------------------------------------------------
void colorReduce4(Mat &image, int div=64) {
int nl= image.rows; //行数
int nc= image.cols * image.channels(); //每行元素的总元素数量
int n= static_cast<int>(log(static_cast<double>(div))/log(2.0));
int step= image.step; //有效宽度
//掩码值
uchar mask= 0xFF<<n; // e.g. 对于 div=16, mask= 0xF0
//获取指向图像缓冲区的指针
uchar *data= image.data;
for (int j=0; j<nl; j++)
{
for (int i=0; i<nc; i++) 
{
//-------------开始处理每个像素-------------------
*(data+i)= *data&mask + div/2;
//-------------结束像素处理------------------------
} //单行处理结束              
data+= step;  // next line
}
}
//---------------------------------------【方法六】----------------------------------------------
// 说明:利用 .ptr 和 * ++以及位运算、image.cols * image.channels()
//-------------------------------------------------------------------------------------------------
void colorReduce5(Mat &image, int div=64) {
int nl= image.rows; //行数
int n= static_cast<int>(log(static_cast<double>(div))/log(2.0));
//掩码值
uchar mask= 0xFF<<n; // e.g. 例如div=16, mask= 0xF0
for (int j=0; j<nl; j++) 
{
uchar* data= image.ptr<uchar>(j);
for (int i=0; i<image.cols * image.channels(); i++) 
{
//-------------开始处理每个像素-------------------
*data++= *data&mask + div/2;
//-------------结束像素处理------------------------
} //单行处理结束            
}
}
// -------------------------------------【方法七】----------------------------------------------
// 说明:利用.ptr 和 * ++ 以及位运算(continuous)
//-------------------------------------------------------------------------------------------------
void colorReduce6(Mat &image, int div=64) {
int nl= image.rows; //行数
int nc= image.cols * image.channels(); //每行元素的总元素数量
if (image.isContinuous())  
{
//无填充像素
nc= nc*nl; 
nl= 1;  // 为一维数列
}
int n= static_cast<int>(log(static_cast<double>(div))/log(2.0));
//掩码值
uchar mask= 0xFF<<n; // e.g. 比如div=16, mask= 0xF0
for (int j=0; j<nl; j++) {
uchar* data= image.ptr<uchar>(j);
for (int i=0; i<nc; i++) {
//-------------开始处理每个像素-------------------
*data++= *data&mask + div/2;
//-------------结束像素处理------------------------
} //单行处理结束                   
}
}
//------------------------------------【方法八】------------------------------------------------
// 说明:利用 .ptr 和 * ++ 以及位运算 (continuous+channels)
//-------------------------------------------------------------------------------------------------
void colorReduce7(Mat &image, int div=64) {
int nl= image.rows; //行数
int nc= image.cols ; //列数
if (image.isContinuous())  
{
//无填充像素
nc= nc*nl; 
nl= 1;  // 为一维数组
}
int n= static_cast<int>(log(static_cast<double>(div))/log(2.0));
//掩码值
uchar mask= 0xFF<<n; // e.g. 比如div=16, mask= 0xF0
for (int j=0; j<nl; j++) {
uchar* data= image.ptr<uchar>(j);
for (int i=0; i<nc; i++) {
//-------------开始处理每个像素-------------------
*data++= *data&mask + div/2;
*data++= *data&mask + div/2;
*data++= *data&mask + div/2;
//-------------结束像素处理------------------------
} //单行处理结束                    
}
}
// -----------------------------------【方法九】 ------------------------------------------------
// 说明:利用Mat_ iterator
//-------------------------------------------------------------------------------------------------
void colorReduce8(Mat &image, int div=64) {
//获取迭代器
Mat_<Vec3b>::iterator it= image.begin<Vec3b>();
Mat_<Vec3b>::iterator itend= image.end<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;
//-------------结束像素处理------------------------
}//单行处理结束  
}
//-------------------------------------【方法十】-----------------------------------------------
// 说明:利用Mat_ iterator以及位运算
//-------------------------------------------------------------------------------------------------
void colorReduce9(Mat &image, int div=64) {
// div必须是2的幂
int n= static_cast<int>(log(static_cast<double>(div))/log(2.0));
//掩码值
uchar mask= 0xFF<<n; // e.g. 比如 div=16, mask= 0xF0
// 获取迭代器
Mat_<Vec3b>::iterator it= image.begin<Vec3b>();
Mat_<Vec3b>::iterator itend= image.end<Vec3b>();
//扫描所有元素
for ( ; it!= itend; ++it) 
{
//-------------开始处理每个像素-------------------
(*it)[0]= (*it)[0]&mask + div/2;
(*it)[1]= (*it)[1]&mask + div/2;
(*it)[2]= (*it)[2]&mask + div/2;
//-------------结束像素处理------------------------
}//单行处理结束  
}
//------------------------------------【方法十一】---------------------------------------------
// 说明:利用Mat Iterator_
//-------------------------------------------------------------------------------------------------
void colorReduce10(Mat &image, int div=64) {
//获取迭代器
Mat_<Vec3b> cimage= image;
Mat_<Vec3b>::iterator it=cimage.begin();
Mat_<Vec3b>::iterator itend=cimage.end();
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;
//-------------结束像素处理------------------------
}
}
//--------------------------------------【方法十二】--------------------------------------------
// 说明:利用动态地址计算配合at
//-------------------------------------------------------------------------------------------------
void colorReduce11(Mat &image, int div=64) {
int nl= image.rows; //行数
int nc= image.cols; //列数
for (int j=0; j<nl; j++) 
{
for (int i=0; i<nc; i++) 
{
//-------------开始处理每个像素-------------------
image.at<Vec3b>(j,i)[0]= image.at<Vec3b>(j,i)[0]/div*div + div/2;
image.at<Vec3b>(j,i)[1]= image.at<Vec3b>(j,i)[1]/div*div + div/2;
image.at<Vec3b>(j,i)[2]= image.at<Vec3b>(j,i)[2]/div*div + div/2;
//-------------结束像素处理------------------------
} //单行处理结束                 
}
}
//----------------------------------【方法十三】----------------------------------------------- 
// 说明:利用图像的输入与输出
//-------------------------------------------------------------------------------------------------
void colorReduce12(const Mat &image, //输入图像
Mat &result,      // 输出图像
int div=64) {
int nl= image.rows; //行数
int nc= image.cols ; //列数
//准备好初始化后的Mat给输出图像
result.create(image.rows,image.cols,image.type());
//创建无像素填充的图像
nc= nc*nl; 
nl= 1;  //单维数组
int n= static_cast<int>(log(static_cast<double>(div))/log(2.0));
//掩码值
uchar mask= 0xFF<<n; // e.g.比如div=16, mask= 0xF0
for (int j=0; j<nl; j++) {
uchar* data= result.ptr<uchar>(j);
const uchar* idata= image.ptr<uchar>(j);
for (int i=0; i<nc; i++) {
//-------------开始处理每个像素-------------------
*data++= (*idata++)&mask + div/2;
*data++= (*idata++)&mask + div/2;
*data++= (*idata++)&mask + div/2;
//-------------结束像素处理------------------------
} //单行处理结束                   
}
}
//--------------------------------------【方法十四】------------------------------------------- 
// 说明:利用操作符重载
//-------------------------------------------------------------------------------------------------
void colorReduce13(Mat &image, int div=64) {
int n= static_cast<int>(log(static_cast<double>(div))/log(2.0));
//掩码值
uchar mask= 0xFF<<n; // e.g. 比如div=16, mask= 0xF0
//进行色彩还原
image=(image&Scalar(mask,mask,mask))+Scalar(div/2,div/2,div/2);
}
//-----------------------------------【ShowHelpText( )函数】-----------------------------
// 描述:输出一些帮助信息
//----------------------------------------------------------------------------------------------
void ShowHelpText()
{
//输出欢迎信息和OpenCV版本
printf("\n\n\t\t\t非常感谢购买《OpenCV3编程入门》一书!\n");
printf("\n\n\t\t\t此为本书OpenCV2版的第24个配套示例程序\n");
printf("\n\n\t\t\t   当前使用的OpenCV版本为:" CV_VERSION );
printf("\n\n  ----------------------------------------------------------------------------\n");
printf("\n\n正在进行存取操作,请稍等……\n\n");
}
//-----------------------------------【main( )函数】--------------------------------------------
// 描述:控制台应用程序的入口函数,我们的程序从这里开始
//-------------------------------------------------------------------------------------------------
int main( )
{
int64 t[NTESTS],tinit;
Mat image0;
Mat image1;
Mat image2;
system("color 4F");
ShowHelpText();
image0= imread("1.png");
if (!image0.data)
return 0; 
//时间值设为0
for (int i=0; i<NTESTS; i++)
t[i]= 0;
// 多次重复测试
int n=NITERATIONS;
for (int k=0; k<n; k++)
{
cout << k << " of " << n << endl; 
image1= imread("1.png");
//【方法一】利用.ptr 和 []
tinit= getTickCount();
colorReduce0(image1);
t[0]+= getTickCount()-tinit;
//【方法二】利用 .ptr 和 * ++ 
image1= imread("1.png");
tinit= getTickCount();
colorReduce1(image1);
t[1]+= getTickCount()-tinit;
//【方法三】利用.ptr 和 * ++ 以及模操作
image1= imread("1.png");
tinit= getTickCount();
colorReduce2(image1);
t[2]+= getTickCount()-tinit;
//【方法四】 利用.ptr 和 * ++ 以及位操作
image1= imread("1.png");
tinit= getTickCount();
colorReduce3(image1);
t[3]+= getTickCount()-tinit;
//【方法五】 利用指针的算术运算
image1= imread("1.png");
tinit= getTickCount();
colorReduce4(image1);
t[4]+= getTickCount()-tinit;
//【方法六】利用 .ptr 和 * ++以及位运算、image.cols * image.channels()
image1= imread("1.png");
tinit= getTickCount();
colorReduce5(image1);
t[5]+= getTickCount()-tinit;
//【方法七】利用.ptr 和 * ++ 以及位运算(continuous)
image1= imread("1.png");
tinit= getTickCount();
colorReduce6(image1);
t[6]+= getTickCount()-tinit;
//【方法八】利用 .ptr 和 * ++ 以及位运算 (continuous+channels)
image1= imread("1.png");
tinit= getTickCount();
colorReduce7(image1);
t[7]+= getTickCount()-tinit;
//【方法九】 利用Mat_ iterator
image1= imread("1.png");
tinit= getTickCount();
colorReduce8(image1);
t[8]+= getTickCount()-tinit;
//【方法十】 利用Mat_ iterator以及位运算
image1= imread("1.png");
tinit= getTickCount();
colorReduce9(image1);
t[9]+= getTickCount()-tinit;
//【方法十一】利用Mat Iterator_
image1= imread("1.png");
tinit= getTickCount();
colorReduce10(image1);
t[10]+= getTickCount()-tinit;
//【方法十二】 利用动态地址计算配合at
image1= imread("1.png");
tinit= getTickCount();
colorReduce11(image1);
t[11]+= getTickCount()-tinit;
//【方法十三】 利用图像的输入与输出
image1= imread("1.png");
tinit= getTickCount();
Mat result;
colorReduce12(image1, result);
t[12]+= getTickCount()-tinit;
image2= result;
//【方法十四】 利用操作符重载
image1= imread("1.png");
tinit= getTickCount();
colorReduce13(image1);
t[13]+= getTickCount()-tinit;
//------------------------------
}
//输出图像   
imshow("原始图像",image0);
imshow("结果",image2);
imshow("图像结果",image1);
// 输出平均执行时间
cout << endl << "-------------------------------------------" << endl << endl;
cout << "\n【方法一】利用.ptr 和 []的方法所用时间为 " << 1000.*t[0]/getTickFrequency()/n << "ms" << endl;
cout << "\n【方法二】利用 .ptr 和 * ++ 的方法所用时间为" << 1000.*t[1]/getTickFrequency()/n << "ms" << endl;
cout << "\n【方法三】利用.ptr 和 * ++ 以及模操作的方法所用时间为" << 1000.*t[2]/getTickFrequency()/n << "ms" << endl;
cout << "\n【方法四】利用.ptr 和 * ++ 以及位操作的方法所用时间为" << 1000.*t[3]/getTickFrequency()/n << "ms" << endl;
cout << "\n【方法五】利用指针算术运算的方法所用时间为" << 1000.*t[4]/getTickFrequency()/n << "ms" << endl;
cout << "\n【方法六】利用 .ptr 和 * ++以及位运算、channels()的方法所用时间为" << 1000.*t[5]/getTickFrequency()/n << "ms" << endl;
cout << "\n【方法七】利用.ptr 和 * ++ 以及位运算(continuous)的方法所用时间为" << 1000.*t[6]/getTickFrequency()/n << "ms" << endl;
cout << "\n【方法八】利用 .ptr 和 * ++ 以及位运算 (continuous+channels)的方法所用时间为" << 1000.*t[7]/getTickFrequency()/n << "ms" << endl;
cout << "\n【方法九】利用Mat_ iterator 的方法所用时间为" << 1000.*t[8]/getTickFrequency()/n << "ms" << endl;
cout << "\n【方法十】利用Mat_ iterator以及位运算的方法所用时间为" << 1000.*t[9]/getTickFrequency()/n << "ms" << endl;
cout << "\n【方法十一】利用Mat Iterator_的方法所用时间为" << 1000.*t[10]/getTickFrequency()/n << "ms" << endl;
cout << "\n【方法十二】利用动态地址计算配合at 的方法所用时间为" << 1000.*t[11]/getTickFrequency()/n << "ms" << endl;
cout << "\n【方法十三】利用图像的输入与输出的方法所用时间为" << 1000.*t[12]/getTickFrequency()/n << "ms" << endl;
cout << "\n【方法十四】利用操作符重载的方法所用时间为" << 1000.*t[13]/getTickFrequency()/n << "ms" << endl;
waitKey();
return 0;

}



2016-11-06 21:55:48 keith_bb 阅读数 3997

在图像处理中不可避免的要涉及到对图像像素的操作,这篇文章将介绍对图像像素的访问及遍历图像像素的方法。

1.颜色空间缩减及查找表

设想一种简单的C\C++类型的无符号字符型矩阵的存储结构,对于单通道图像而言,图像像素最多可以由256个像素值。如果图像是三通道图像,那么图像像素存储的颜色可以达到惊人的1600w。处理如此多的颜色类型对于算法的运算是一种沉重的负担。有时候我们可以找到一些既能够降低颜色数量但是并不会影响其处理结果的方法。通常我们缩减颜色空间。这就意味着我们用新输入的数值和更少的颜色来划分当前的颜色空间。
例如我们可以将值在0-9范围内的像素值看做0,将值位于10-19范围内的像素值看做10等等。当我们用int类型的数值代替uchar(unsigned char-值位于0-255之间)类型得到的结果仍为char类型。这些数值只是char类型的值,所以求出来的小数要向下取整。公式可以总结如下:
这里写图片描述
遍历整幅图像像素并应用上述公式就是一个简单的颜色空间缩减算法。对于较大的图像需要在执行操作可以前提前计算好其像素值存储到查找表中。查找变是一种简单的数组(可能是一维或多维),对于给定的输入变量给出最终的输出值。在进行图像处理时,像素取值范围在0-255之间其实就是一共有256种情况,所以将这些计算结果提前存储于查找表中,进行图像处理时,不需要重新计算像素值,可以直接从查找表调用。其优势在于只进行读取操作,不进行运算。
结合上述公和查找表如下:

    int divideWith = 0;
    stringstream s;
    s << argv[2];
    s >> divideWith;
    if(!s || !divideWith)
    {
        cout << "输入的划分间隔无效." << endl;
        return -1;
    }
    uchar table[256];
    for(int i = 0;i < 256; ++i)
        table[i] = (uchar)(divideWith * (i * divideWith));

程序中table[i]存放的是值为i的像素缩减空间的结果。例如i = 25,则table[i]=20.这样看来颜色空间缩减算法可分为两部分:
(1).遍历图像矩阵像素
(2).将上述公式应用于每个像素
值得注意的是,此公式用到了乘法和除法,而这两种计算方式相对来讲比较费时,所以在设计像素缩减空间算法时,应尽量使用加减和赋值运算代替。

2.opencv计时函数

在上面分析中提到用乘除法会加大程序的耗时,那么怎么计算程序运行中的耗时呢?opencv中提供了两个简便的计时函数getTickCount()和getTickFrequency()。其中getTickCount()用来获取CPU时钟周期,getTickFrequency()函数用来获取CPU时钟频率。这样就能以秒为单位对程序运行进行耗时分析,其用法如下:

    double t = (double)getTickCount();
    //...
    //program...
    //...
    t = ((double)getTickCount()-t)/getTickFrequency();

3.图像矩阵在内存中的存储方式
opencv学习(一)之Mat类中介绍Mat类的创建等内容,同时也应该能够了解到图像的数据结构为Mat类,是一种矩阵结构。图像矩阵大小取决于所用的颜色模型,更确切的来说是取决于图像所用通道数。如果是灰度图像,其矩阵结构如下图所示:
这里写图片描述
对于多通道图像来说,矩阵的列会包含多个子列,其子列个数与通道数相等。RGB颜色模型矩阵如下图所示:
这里写图片描述
在opencv中图像的RGB顺序如上图所示正好是反过来的,其排序为BGR。在很多情况下可以如果内存足够大可以实现连续存储。连续存储有助于提升图像扫描速度,可以使用isContinuous()来判断矩阵是否是连续存储。

当涉及到程序性能时,没有比C风格的操作符”[]”(指针)更高效了。测试代码如下:

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

using namespace std;
using namespace cv;

Mat& ScanImageAndReduce(Mat& I,const uchar* const table);

int main(int argc, char** argv)
{
    int divideWith = 0;

    Mat srcImage = imread("lena.jpg");

    //image is load sucessful?
    if(srcImage.data)
        cout << "Success" << endl;
    else
        return -1;

    imshow("srcImage",srcImage);

    cout << "input divideWith: ";
    cin >> divideWith;

    if(!divideWith)
    {
        cout << "输入的划分间隔无效." << endl;
        return -1;
    }
    uchar table[256];
    for(int i = 0;i < 256; ++i)
        table[i] = (uchar)(divideWith * (i * divideWith));

    ScanImageAndReduce(srcImage,table);
    waitKey(0);

    return 0;
}

Mat& ScanImageAndReduce(Mat& I, const uchar* const table)
{
    CV_Assert(I.depth() == CV_8U);

    //定义变量与原图像保持一致
    int channels = I.channels();
    int nRows = I.rows;
    int nCols = I.cols * channels;

    //判断矩阵是否是连续矩阵
    if(I.isContinuous())
    {
        nCols *= nRows;
        nRows = 1;
    }

    int i,j;
    uchar* p;
    for(i = 0; i < nRows; ++i)
    {
        p = I.ptr<uchar>(i);        //获取矩阵第i行的首地址
        for(j = 0; j < nCols; ++j)  //列循环进行处理
        {
            p[j] = table[p[j]];
        }
    }
    imshow("reduce-100",I);         //根据输入值对窗口名字进行更改

    return I;
}

当设置不同的结果时,其最终输出结果不同如下所示:
这里写图片描述
这里写图片描述
这里写图片描述

2019-08-28 09:33:17 dafenqie 阅读数 66

        Emgucv以及Opencv封装了大量的API,但大多数图像处理算法内部都是从操作每个像素开始的。有时候在工程应用中,我们不得不遍历图像的每个像素来实现我们的算法。

       举一个应用场景说明:一张图像的矩阵元素若存储的是3通道像素值,那么每个像素值的取值共有256*256*256种,用如此多的颜色来进行处理,会对我们的算法造成严重的影响,其实,我们只需要一部分像素值就可以了,这时候我们就可以遍历图像的每个像素来颜色空间缩减一下。

颜色空间缩减算法:给定一个整数,像素值/给定整数*给定整数。

     Emgucv版本:4.1.0

     .Net版本:4.6.1

(一)利用Image<Bgr,byte>遍历像素

(二)利用指针直接访问内存遍历像素

(三)测试效果

      采用的Example.jpg输入图像分辨率为533*300。

       在同一台机器上测试第一种算法执行时间为15ms左右,第二种算法1ms左右,很明显利用指针直接操作内存遍历像素效率可以提升7倍左右。

       在对效率要求不太高的应用场景中,建议使用第一种算法,在.Net框架里使用比较方便。第二种算法虽然效率比较高,但对指针以及图像在内存中的分布要求较高,在C#里不建议使用非托管特性。

      源代码参见:https://download.csdn.net/download/dafenqie/11629006

       需要源码以及更过例程的朋友请加qq群609905705,专业的Emgucv学习群。

2019-08-18 22:54:33 Docterzzz 阅读数 15

图像处理基础–像素遍历,浅拷贝,深拷贝

这篇博客主要根据高翔的《视觉SLAM14讲》的开源代码进行学习,在Windows上,采用VS2019对代码进行编译。

find_package( OpenCV 3 REQUIRED )
set(OpenCV_DIR "D:/Code/SLAM/ORB_SLAM2/Thirdparty/opencv/sources/build/install/lib")
find_package(OpenCV REQUIRED)

修改opencv的路径

采用VS2019对代码进行编译的,修改CMakeList.txt,之后,可以生成成功。
由于代码用的不是utf8,可能需要在VS中修改代码为utf8格式,修改后,可以编译通过。
运行阶段,会报缺少opencv_highgui2413d.dll动态库的问题,复制对应的动态库到Debug目录下即可。或者把库文件对应的路径添加到环境变量里。

代码实现的功能

遍历图像

for ( size_t y=0; y<image.rows; y++ )
    {
        // 用cv::Mat::ptr获得图像的行指针
        unsigned char* row_ptr = image.ptr<unsigned char> ( y );  // row_ptr是第y行的头指针
        for ( size_t x=0; x<image.cols; x++ )
        {
            // 访问位于 x,y 处的像素
            unsigned char* data_ptr = &row_ptr[ x*image.channels() ]; // data_ptr 指向待访问的像素数据
            // 输出该像素的每个通道,如果是灰度图就只有一个通道
            for ( int c = 0; c != image.channels(); c++ )
            {
                unsigned char data = data_ptr[c]; // data为I(x,y)第c个通道的值
            }
        }
    }

image的浅拷贝和深拷贝

浅拷贝,直接复制,并不会拷贝数据,image和image_another 还使用的是同一套数据

cv::Mat image_another = image;

深拷贝,保存有两套数据

cv::Mat image_clone = image.clone();

EmguCV遍历图像像素

阅读数 7374

没有更多推荐了,返回首页