1.IplImage
- IplImage是OpenCV2、3 中CxCore部分基础的数据结构,用来表示图像。IplImage结构体如下所示:
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;
- 对我们来说比较重要的两个元素是:char *imageData以及widthStep
- imageData指向"存储图像数据的一块数据区”、“排列的图像行大小”。 我们都知道,一张图是由无数个像素点构成的,每个像素点的像素值都不同所以我们看到的图片才具有丰富的颜色。 imageData就是一个指针,指向某张图片像素值数据的首地址。 如:(uchar )frameimg->imageData就是图像第一行首地址。
- widthStep表示存储一行像素需要的字节数。因为opencv分配的内存是按4字节对齐的,所以widthStep必须是4的倍数,如果8U图像宽度为3,那么widthStep是4,加一个字节补齐。这个图像的一行需要4个字节,只使用前3个,最后一个空在那儿不用。也就是一个宽3高3的图像的imageData数据大小为43=12字节。
直接访问
///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;
}
指针访问
- 1.行遍历
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);
}
}
- 列遍历
//单通道-灰度图
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隐式转换为整型,否则会打印出字符
}
}
不同通道指针遍历
- 1、单通道字节型图像像素访问:
#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;
}
- 三通道字节型图像像素访问
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
- opencv3、4中的图像数据结构,组成如下:
class CV_EXPORTS Mat
{
public:
//一系列函数
...
/* flag 参数中包含许多关于矩阵的信息,如:
-Mat 的标识
-数据是否连续
-深度
-通道数目
*/
int flags;
//矩阵的维数,取值应该大于或等于 2
int dims;
//矩阵的行数和列数,如果矩阵超过 2 维,这两个变量的值都为-1
int rows, cols;
//指向数据的指针
uchar* data;
//指向引用计数的指针
//如果数据是由用户分配的,则为 NULL
int* refcount;
//其他成员变量和成员函数
...
};
- 属性调用:
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();
遍历灰度图像
- 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);
}
}
}
遍历彩色图像
- 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;
}
}
//methed_1 数组访问 Spendtime:11.1927ms.
//methed_2 Vec3b指针 Spendtime:6.3479ms.
//methed_3 字节指针 Spendtime:0.1558ms.
- 更加高效的方法:
//二、高效一点:用指针来遍历图像
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;
}
}
}
//更高效的方法
//一般来说图像行与行之间往往存储是不连续的,但是有些图像可以是连续的,
//Mat提供了一个检测图像是否连续的函数isContinuous。当图像连通时,我们就可以把图像完全展开,看成是一行。
//方法四
//更高效的方法
//一般来说图像行与行之间往往存储是不连续的,但是有些图像可以是连续的,
//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);
参考:
1.OpenCV学习 之 IplImage*遍历每个像素点
2.OpenCV 中像素遍历常用的几种方法