Detailed explanation of image traversal method of opencv

1.IplImage

  • IplImage is the basic data structure of CxCore in OpenCV2 and 3, which is used to represent images. The IplImage structure is as follows:
 typedef struct _IplImage  
    {
    
      
         int  nSize;         /* IplImage大小 */  
         int  ID;            /* 版本 (=0)*/  
         int  nChannels;     /* 大多数OPENCV函数支持1,2,3 或 4 个通道 */  
         int  alphaChannel;  /* 被OpenCV忽略 */  
         int  depth;         /* 像素的位深度: IPL_DEPTH_8U, IPL_DEPTH_8S, IPL_DEPTH_16U, 
                                IPL_DEPTH_16S, IPL_DEPTH_32S, IPL_DEPTH_32F and IPL_DEPTH_64F 可支持 */  
         char colorModel[4]; /* 被OpenCV忽略 */  
         char channelSeq[4]; /* 同上 */  
         int  dataOrder;     /* 0 - 交叉存取颜色通道, 1 - 分开的颜色通道. 
                                cvCreateImage只能创建交叉存取图像 */  
         int  origin;        /* 0 - 顶—左结构, 
                                1 - 底—左结构 (Windows bitmaps 风格) */  
         int  align;         /* 图像行排列 (4 or 8). OpenCV 忽略它,使用 widthStep 代替 */  
         int  width;         /* 图像宽像素数 */  
         int  height;        /* 图像高像素数*/  
         struct _IplROI *roi;/* 图像感兴趣区域. 当该值非空只对该区域进行处理 */  
         struct _IplImage *maskROI; /* 在 OpenCV中必须置NULL */  
         void  *imageId;     /* 同上*/  
         struct _IplTileInfo *tileInfo; /*同上*/  
         int  imageSize;     /* 图像数据大小(在交叉存取格式下imageSize=image->height*image->widthStep),单位字节*/  
         char *imageData;  /* 指向排列的图像数据 */  
         int  widthStep;   /* 排列的图像行大小,以字节为单位 */  
         int  BorderMode[4]; /* 边际结束模式, 被OpenCV忽略 */  
         int  BorderConst[4]; /* 同上 */  
        char *imageDataOrigin; /* 指针指向一个不同的图像数据结构(不是必须排列的),是为了纠正图像内存分配准备的 */  
     }  
     IplImage;  

  • The two elements that are more important to us are: char *imageData and widthStep
    • imageData points to "a data area for storing image data" and "the size of arranged image rows". We all know that a picture is composed of countless pixels, and the pixel value of each pixel is different, so the pictures we see have rich colors. imageData is a pointer, pointing to the first address of the pixel value data of a picture. For example: (uchar) frameimg->imageData is the first address of the first line of the image.
    • widthStep indicates the number of bytes required to store a row of pixels. Because the memory allocated by opencv is aligned by 4 bytes, the widthStep must be a multiple of 4. If the 8U image width is 3, then the widthStep is 4, and one byte is added to fill it up. One line of this image requires 4 bytes, only the first 3 are used, and the last one is left blank. That is, the imageData data size of an image with a width of 3 and a height of 3 is 43=12 bytes.

direct interview

///IplImage* src 图像遍历的N种方法.
/// <summary>
/// OK
/// 灰度图像像素遍历.直接访问.
/// </summary>
/// <param name="src"></param>输入:灰度图像.
void f_grayImageRow(IplImage* src)
{
    
    
	if (NULL == src->imageData)
	{
    
    
		printf("src not exist!!!");
		return;
	}
 
	int nchannel = src->nChannels;
 
	//IplImage* img = cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, 1);
	
	uchar* temp = new uchar;
	if (1 == nchannel)
	{
    
    
		for (int i = 0; i < src->height; i++)//行遍历
		{
    
    
			for (int j = 0; j < src->width; j++)
			{
    
    
				*temp = ((uchar*)(src->imageData + i * src->widthStep))[j];
 
				if ((i > 10 && i < 50) && (j > 10 && j < 80))
				{
    
    
					((uchar*)(src->imageData + i * src->widthStep))[j] = 0;
				}
			}
		}
 
		//cvSaveImage("dst.jpg", src);
		cvNamedWindow("gray", 0);
		cvShowImage("gray", src);
		cvWaitKey(0);
	}
	delete temp;
}

pointer access

  • 1. Row traversal
void f_grayImageRow1(IplImage* src)
{
    
    
	if (NULL==src->imageData)
	{
    
    
		printf("src not exist!!!");
		return;
	}
 
	int i=0;
	int j=0;
	int nchannel = src->nChannels;
 
	//单通道-灰度图
	uchar* upixel = nullptr;
	if (1 == nchannel)
	{
    
    
		//方式1:
		for (i = 0; i < src->height; i++)//行遍历
		{
    
    
			upixel = (uchar*)(src->imageData + i * src->widthStep);
 
			for (j = 0; j < src->width; j++)
			{
    
    
				if ((i > 10 && i < 50) && (j > 10 && j < 80))
				{
    
    
					upixel[j] = 0;
				}
 
				//std::cout << "upixel=" << (*upixel) + 0 << std::endl;//+0隐式转换为整型,否则会打印出字符
			}
		}

        //方式2:
		for (i = 0; i < src->height; i++)//行遍历
		{
    
    
			for (j = 0; j < src->width; j++)
			{
    
    
				upixel = (uchar*)(src->imageData + i * src->widthStep + j);
 
				if ((i > 10 && i < 50) && (j > 10 && j < 80))
				{
    
    
					*upixel = 0;
				}
 
				//std::cout << "upixel=" << (*upixel) + 0 << std::endl;//+0隐式转换为整型,否则会打印出字符
			}
		}
		//cvSaveImage("dst1.jpg", src);
		cvNamedWindow("gray1",0);
		cvShowImage("gray1", src);
		cvWaitKey(0);
	}
}
  • column traversal
//单通道-灰度图
	uchar *upixel = nullptr;
	if (1 == nchannel)
	{
    
    
		//方式1:
		for (x = 0; x < src->width; x++)//列遍历
		{
    
    
			for (y = 0; y < src->height; y++)
			{
    
    
				//if ((i > 10 && i < 50) && (j > 10 && j < 80))
				{
    
    
					upixel = (uchar*)(src->imageData + y * src->widthStep + x);
					*upixel = 0;
				}
				cvNamedWindow("gray111",0);
				cvShowImage("gray111", src);
				cvWaitKey(1);
				//std::cout << "upixel=" << (*upixel) + 0 << std::endl;//+0隐式转换为整型,否则会打印出字符
			}
		}

Different channel pointer traversal

  • 1. Single-channel byte image pixel access:
    insert image description here
#include <cv.h>  
#include <highgui.h>  
 using namespace std;
using namespace cv;
int main(void)
{
    
    
IplImage* imgSrc = cvLoadImage("./inputData\\shuke1.jpg",0);
uchar* pixel = new uchar;
for (int i = 0; i < imgSrc->height; i++)  //遍历每一行
{
    
    
for (int j = 0; j < imgSrc->width; j++) //遍历每一列
{
    
    
pixel = (uchar*)(imgSrc->imageData + i*imgSrc->widthStep+j);
cout << "pixel=" <<(*pixel)+0<< endl;//+0隐式转换为整型,否则会打印出字符
}
}
delete pixel;
     return 0;
 }

  • Three-channel byte image pixel access
    insert image description here
IplImage* img=cvCreateImage(cvSize(640,480),IPL_DEPTH_8U,3);
uchar* data=(uchar *)img->imageData;
int step = img->widthStep/sizeof(uchar);   //因为是三通道,所以要做一下这个操作
int channels = img->nChannels;             //这个图片为3通道的
uchar *b,*g,*r;
for(int i=0;iheight;i++)
     for(int j=0;jwidth;j++){
    
    
           *b=data[i*step+j*chanels+0];    //此时可以通过更改bgr的值达到访问效果。
           *g=data[i*step+j*chanels+1];
           *r=data[i*step+j*chanels+2];
      }

2.Mat

  • The image data structure in opencv3 and 4 is composed as follows:
class CV_EXPORTS Mat
{
    
    
public:
//一系列函数
...
 
/* flag 参数中包含许多关于矩阵的信息,如:
      -Mat 的标识
      -数据是否连续
      -深度
      -通道数目
*/
int flags;
 
//矩阵的维数,取值应该大于或等于 2
int dims;
 
//矩阵的行数和列数,如果矩阵超过 2 维,这两个变量的值都为-1
int rows, cols;
 
//指向数据的指针
uchar* data;
 
//指向引用计数的指针
//如果数据是由用户分配的,则为 NULL
int* refcount;
 
//其他成员变量和成员函数
...
 
};
  • Property call:
    cv::Mat src = cv::imread("..\\testPicture\\happyfish.jpg", cv::IMREAD_COLOR);
    cv::namedWindow("0", 0);
    cv::imshow("0", src);
    cv::waitKey(0);
 
	cv::Mat grayImg;
	cv::cvtColor(src, grayImg, CV_RGB2GRAY);
 
 
	int xwCol = src.cols;
	int yhRow = src.rows;
	int nchannels = src.channels();

Iterate over grayscale images

  • case1:
 
	if (1==nchannels)
	{
    
    
		for (int y = 0; y < yhRow; y++)//行遍历
		{
    
    
			uchar* ptr = src.ptr<uchar>(y);
			for (int x = 0; x < xwCol; x++)
			{
    
    
				ptr[x] = 255 - ptr[x];
			}
		}
	}
  • case2:
	if (1==nchannels)
	{
    
    
		for (int i = 0 ; i < src.rows ; i ++)
		{
    
    
			for(int j = 0 ; j < src.cols ; j ++)
			{
    
    
				src.at<uchar>(i,j) = 255-src.at<uchar>(i,j);
			}
		}
	}

iterate over color images

  • method1:
	//方法1:at<typename>(i,j)
	//基于Mat对象的随机像素访问API实现,通过行列索引方式遍历每个像素值。
	for (int y = 0; y < yhRow; y++)
	{
    
    
		for (int x = 0; x < xwCol; x++)
		{
    
    
			cv::Vec3b bgr = src.at<cv::Vec3b>(y, x);
			bgr[0] = 255 - bgr[0];
			bgr[1] = 255 - bgr[1];
			bgr[2] = 255 - bgr[2];
	
			src.at<cv::Vec3b>(y, x) = bgr;
		}
	}
  • method2:
	//方法2
	//基于Mat对象的行随机访问指针方式实现对每个像素的遍历.
	for (int y = 0; y < yhRow; y++)
	{
    
    
		cv::Vec3b* curR = src.ptr<cv::Vec3b>(y);
		for (int x = 0; x < xwCol; x++)
		{
    
    
			cv::Vec3b bgr = curR[x];
			bgr[0] = 255 - bgr[0];
			bgr[1] = 255 - bgr[1];
			bgr[2] = 255 - bgr[2];
	
			//src.at<cv::Vec3b>(y, x) = bgr;//OK
			curR[x] = bgr;//OK
		}
	}
  • method3:
	//方法三
	//直接获取Mat对象的像素块的数据指针,基于指针操作,实现快速像素方法.
	for (int y = 0; y < yhRow; y++)
	{
    
    
		uchar* uc_pixel = src.data + y * src.step;
		for (int x = 0; x < xwCol; x++)
		{
    
    
			uc_pixel[0] = 255 - uc_pixel[0];
			uc_pixel[1] = 255 - uc_pixel[1];
			uc_pixel[2] = 255 - uc_pixel[2];
 
			uc_pixel += 3;
		}
	}
//或者
    for (int i = 0; i < imgsList.size(); ++i)
    {
    
    
        cv::Mat img = cv::imread(imgsList[i]);
        cv::Mat mask(img.size(), img.type(), cv::Scalar::all(0));
        for (int i = 0; i < img.rows; ++i)
        {
    
    
            uchar* _data = img.data + img.step * i;
            uchar* _mask = mask.data + mask.step * i;
            for (int j = 0; j < img.cols; ++j)
            {
    
    
                for (int k = 0; k < img.channels(); ++k)
                {
    
    
                    _mask[j * 3+ k] = _data[j * 3 + k];
                }
            }
        }
    }

//methed_1 array access Spendtime: 11.1927ms.
//methed_2 Vec3b pointer Spendtime: 6.3479ms.
//methed_3 byte pointer Spendtime: 0.1558ms .

  • A more efficient method:
 //二、高效一点:用指针来遍历图像
 void colorReducePtr(const Mat& image, Mat& outImage,int div)
 {
    
    
    //创建与原图像等尺寸的图像
    outImage.create(image.size(),image.type());
	//行数
    int nr=image.rows;  
    //将3通道转换为1通道
    int nl=image.cols*image.channels();//列数*通道数=每一行元素的个数
    
	//双重循环,遍历所有的像素值
	for(int k=0;k<nr;k++) //行循环
    {
    
    
         //每一行图像的指针
        const uchar* inData=image.ptr<uchar>(k);//获取第K行的首地址
        uchar* outData=outImage.ptr<uchar>(k);
        for(int i=0;i<nl;i++) //列循环
        {
    
    
			outData[i] = inData[i]/div*div+div/2;//对每一个像素值进行处理
			//*outData++ = *inData++/div*div+div/2;
        }
    }
}

//More efficient method
//Generally speaking, the storage between image lines is often discontinuous, but some images can be continuous,
//Mat provides a function isContinuous to detect whether the image is continuous. When the image is connected, we can fully expand the image and regard it as a row.

    //方法四
	//更高效的方法	
	//一般来说图像行与行之间往往存储是不连续的,但是有些图像可以是连续的,
	//Mat提供了一个检测图像是否连续的函数isContinuous。当图像连通时,我们就可以把图像完全展开,看成是一行。
	int div = 64;
	cv::Mat outImage;
	outImage.create(src.size(), src.type());
	
	//int yhRow = src.rows;
	//int xwCol = src.cols;
	if (src.isContinuous() && outImage.isContinuous())
	{
    
    
		yhRow = 1;
		xwCol = xwCol * src.rows * src.channels();
	}
	for (int i = 0; i < yhRow; i++)
	{
    
    
		const uchar* inData = src.ptr<uchar>(i);
		uchar* outData = outImage.ptr<uchar>(i);
		for (int j = 0; j < xwCol; j++)
		{
    
    
			//*outData++ = *inData++ / div * div + div / 2;
			*outData++ = 255 - (*inData++);
		}
	}
	cv::namedWindow("case 4", 0);
	cv::imshow("case 4", outImage);
	cv::waitKey(0);

reference:

1. IplImage* of OpenCV learning traverses each pixel
2. Several common methods of pixel traversal in OpenCV

Guess you like

Origin blog.csdn.net/yohnyang/article/details/130388374