opencv之图像遍历方法详解

news2024/9/21 17:36:13

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 中像素遍历常用的几种方法

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/465646.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

LeetCode DFS算法求解联通分量数——省份数量

省份数量 题目要求 有 n 个城市&#xff0c;其中一些彼此相连&#xff0c;另一些没有相连。如果城市 a 与城市 b 直接相连&#xff0c;且城市 b 与城市 c 直接相连&#xff0c;那么城市 a 与城市 c 间接相连。 省份 是一组直接或间接相连的城市&#xff0c;组内不含其他没有相连…

编译时不好的注释会让代码丢失并产生问题

写在之前 这篇文章是上一篇文章的后续事件&#xff0c;记录的事情也挺有意思。想看事情如何开始的点击链接 频繁GC引起卡顿问题排查与解决 进入正题 不知道有没有人遇到过编译后部分代码缺失呢&#xff1f;反正我遇到了&#xff0c; 上一篇文章提到了因为开发人员写了死循环…

Docker安装mysql8.0文档

第一步需要安装Docker基础环境&#xff0c;具体可以看看这篇 docker基础篇 第二步&#xff0c;拉取mysql8.0的镜像 docker pull mysql:8.0 第三步&#xff0c;镜像启动和文件挂载 复制下面命令执行&#xff0c;33006是对外访问暴露的端口&#xff0c;当然你也可以设置为3306…

【Hello Network】HTTP协议

作者&#xff1a;小萌新 专栏&#xff1a;网络 作者简介&#xff1a;大二学生 希望能和大家一起进步 本篇博客简介&#xff1a;较为详细的介绍HTTP协议 HTTP协议 HTTP协议HTTP协议概念URL概念urlencode和urldecodeHTTP协议格式HTTP请求协议格式HTTP响应格式 HTTP的方法HTTP状态…

04-26 每日一题 1031. 两个非重叠子数组的最大和 学习反思

1031. 两个非重叠子数组的最大和 类似问题转换 考虑一个问题&#xff0c;如何求得数组中两个数的最大和。 可以固定一个数&#xff0c;然后向右遍历如下&#xff0c;可以求得目标数组中两个数的最大和为 15 把思路实现为代码 实现过程&#xff0c;如上图所示过程&#xff0…

【汽车品牌案例02-设置右侧索引 Objective-C语言】

一、刚才我们说了一下,如何把那个汽车品牌加载起来,我们使用了一个模型的嵌套,以及我们在创建单元格的时候,是不是指定了一个,单元格的可重用ID吧, 1.根据重用ID来创建单元格,那么我们运行的时候,已经能把这个大致的效果做出来了, 大致就是这么一个效果, 接下来,还…

SPI机制源码解析

概念 SPI&#xff08;service provider interface&#xff09;&#xff0c;是JDK内置的一种服务提供发现机制。是一种动态替换发现机制&#xff0c;比如有个接口&#xff0c;想在运行时动态地给它添件实现&#xff0c;只需要添加一个实现。 然后在META-INF/services目录创建一…

树莓派+摄像头:mjpg-streamer实现监控功能的配置及调试

目录 一 树莓派摄像头安装 二 配置mjpg-streamer ① 说明 ② 配置 <1> 配置前需要安装的工具包 <2> 下载安装mjpg-streamer源码到树莓 <3> 进入下载目录的路径 <4> 输入指令编译&#xff1a;make all <5> 安装指令&#xff1a;…

任务调度原理 通俗详解(FreeRTOS)

寄存器说明 以cortex-M3&#xff0c;首先先要了解比较特别的几个寄存器&#xff1a; r15 PC程序计数器&#xff08;Program Counter&#xff09;,存储下一条要执行的指令的地址。 r14 LR连接寄存器&#xff08;Link Register &#xff09;&#xff0c;保存函数返回地址&#x…

【操作系统】第一章

文章目录 &#x1f337; 一、操作系统的概念1、定义2、功能 和 目标 &#x1f337; 二、操作系统的特征1、**并发**2、 **共享**3、 **虚拟**4、 **不确定性** &#x1f337; 三、操作系统的发展与分类1、 手工操作阶段2、 批处理阶段3、 分时操作系统4、 实时操作系统5、 网络…

servlet技术

功能 对客户端发送的数据进行读取和拦截读取客户端请求的隐含数据运行结果或者生成结果发送响应的数据 Servlet技术特点 高效率 Servlet本身就是一个Java类&#xff0c;在运行的时候位于同一个Java虚拟机中&#xff0c;可以快速地响应客户 端的请求并生成结果。在Wb服务器…

停用词(stop words)+TF-IDF实现

一、什么是停用词&#xff1f; 在汉语中&#xff0c;有一类没有实际意义的词语&#xff0c;比如组词“的”&#xff0c;连词“以及”&#xff0c;副词“甚至”&#xff0c;语气词“吧”&#xff0c;被称为停用词。一个句子去掉这些停用词&#xff0c;并不影响理解。所以&#…

资产管理系统

目录 1、资产管理模块 资产入库 ​编辑 闲置资产分配 资产调配 资产回收 资产报废 车辆维修 2、资产设置模块 资产标准规格 资产分类 3、资产报表模块 全部资产报表 已分配资产报表 资产分类报表 到期资产报表 机构资产报表 资产折旧报表 机构分类报表 资产…

〖ChatGPT实践指南 - 零基础扫盲篇⑤〗- OpenAI API 演示 Demo 之宠物名字生成器

文章目录 ⭐ 运行 Demo应用 - 宠物名字生成器&#x1f31f; 安装 - node.js&#x1f31f; 利用 git 下载 Demo应用 - 宠物名字成器&#x1f31f; 添加 API 秘钥&#x1f31f; 安装依赖并运行Demo应用 - 宠物名字成器 ⭐ 访问并测试 Demo应用 - 宠物名字成器 在上一章节&#xf…

最新版TensorFlow的GPU版本不支持原生Windows系统(大坑预警)

一、前言 首先需要说明&#xff0c;按照官方中文文档安装是无法正常检测到GPU的。因为TensorFlow 2.10是支持原生Windows系统GPU环境的最后版本&#xff0c;默认安装的版本都比较高。 中文文档没有说明&#xff0c;英文文档是有提到的&#xff1a; &#xff08;我在GitHub上找…

PostgreSQL-布尔类型

布尔类型 boolean的值要么是true&#xff0c;要么是false&#xff0c;如果是unknown状态&#xff0c;用NULL表示。 boolean在SQL中可以用不带引号的TRUE或FALSE表示&#xff0c;也可以用其他表示“真”和“假”的带引号字符表示&#xff0c;如true、false、yes、no等等。 cr…

操作系统之进程同步和互斥

目录 什么是进程同步和进程互斥 进程互斥的软件实现方法 进程互斥的硬件实现方法 互斥锁 信号量机制 用信号量实现进程互斥和同步 生产者消费者问题 多生产者多消费者问题 吸烟者问题 读者写者问题 哲学家进餐问题 管程 死锁 什么是进程同步和进程互斥 进程同步 进…

【Golang开发入门】一篇文章弄懂:值类型、指针类型

博主简介&#xff1a;努力学习的大一在校计算机专业学生&#xff0c;热爱学习和创作。目前在学习和分享&#xff1a;数据结构、Go&#xff0c;Java等相关知识。博主主页&#xff1a; 是瑶瑶子啦所属专栏: Go语言核心编程近期目标&#xff1a;写好专栏的每一篇文章 目录 一、前言…

【数据分析之道-Pandas(二)】DataFrame

文章目录 专栏导读1、DataFrame简介2、DataFrame创建2.1字典创建DataFrame2.2列表创建DataFrame2.3ndarrays 创建DataFrame2.4CSV文件创建DataFrame 3、DataFrame基本操作3.1添加列3.2删除列 专栏导读 ✍ 作者简介&#xff1a;i阿极&#xff0c;CSDN Python领域新星创作者&…

什么是 TDSQL-C MySQL版 ---- 数据库开发者视角

我们从设计演化的角度来讲什么是 TDSQL-C MySQL 版本。 首先&#xff0c;我们有了一个纯净版 MySQL。它是一个单机数据库。存算分离&#xff1a;然后&#xff0c;我们把 MySQL 的存储引擎拿掉&#xff0c;换成云存储。这就成了存算分离。这时&#xff0c;在用户看来它还是一个…