OpenCV坐标系与操作像素的四种方法

本文详细介绍图像处理中像素操作的四种方法:at方法、行指针方法、指针方法及迭代方法,并通过实例对比不同方法的效率。同时解释了OpenCV中坐标系的特点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

像素是图像的基本组成单位,熟悉了如何操作像素,就能更好的理解对图像的各种处理变换的实现方式了。


********灰度就是一个通道,彩色就是三个通道!

1.at方法

第一种操作像素的方法是使用“at”,如一幅3通道的彩色图像image的第i行j列的B、G、R分量分别表示为:

image.at<Vec3b>(i,j)[0];

image.at<Vec3b>(i,j)[1];

image.at<Vec3b>(i,j)[2];

而对于单通道的灰度图像就简单很多了:

image.at<uchar>(i,j); 

这里要注意at中(i,j)的顺序表示的是第i行第j列,跟Point(i,j)和Rect(i,j)中表示第j行第i列是相反的,如果把这个搞混了,很容易导致内存异常,还不容易发现错误。

 

补充说明一下:opencv中坐标体系中的零点坐标定义为图片的左上角,X轴为图像矩形的上面那条水平线,从左往右;Y轴为图像矩形左边的那条垂直线,从上往下。在Point(x,y)和Rect(x,y)中,第一个参数x代表的是元素所在图像的列数,第二个参数y代表的是元素所在图像的行数,而在at(x,y)中是相反的。

演示程序如下:

[cpp]  view plain  copy
  1. #include<iostream>  
  2. #include<core/core.hpp>  
  3. #include<highgui/highgui.hpp>  
  4.   
  5. using namespace cv;  
  6. using namespace std;  
  7.   
  8. int main()  
  9. {  
  10.     Mat image(Size(500,500),CV_8UC3);  
  11.     image.at<Vec3b>(100,250)[0]=0;  
  12.     image.at<Vec3b>(100,250)[1]=0;  
  13.     image.at<Vec3b>(100,250)[2]=255;  
  14.     putText(image,"at(100,250) is Here!",Point(250,100),0,0.7,Scalar(255,0,0));  
  15.   
  16.     image.at<Vec3b>(Point(100,250))[0]=0;  
  17.     image.at<Vec3b>(Point(100,250))[1]=0;  
  18.     image.at<Vec3b>(Point(100,250))[2]=255;  
  19.     putText(image,"at(Point(100,250)) is Here!",Point(100,250),0,0.7,Scalar(255,0,0));  
  20.   
  21.     imshow("Test Function at",image);  
  22.     waitKey();  
  23.     return 0;  
  24. }  

 



2.行指针方法

行指针方法的思路是获取图像每一行的首地址的指针,把每一行当成一个循环对象,逐行遍历所有的像素。例如对于一个X行、Y列的图像image,每行需要循环 的数量是Y*image.channels(),channels表示的是图像的通道,对于灰度图像channels为1,对于彩色RGB图像,channels为3。

3.指针方法

对于硬件处理芯片来说,如果图像每行的长度是4或8的倍数的话,运算起来会更加快速,如果不是4或8的倍数的话,在运算前图像行长度会被补充至固定值。
“指针方法”是一种可以高效遍历图像的方式,但是只能针对没有经过填充的连续图像,所以在使用指针方法之前需要先判断图像有没有经过填充,是否连续性。判断的方法很简单,使用Mat的成员函数isContinuous来判断,若返回值为真的话,说明图像是连续的,可以应用行指针的方法遍历像素。

4.迭代方法

使用迭代器遍历图像集合中的各个元素,相比前3中方法,这个方法简直可以永简单快捷的“无脑操作”来形容,你只需获取集合中元素的首地址以及元素的终止位置,剩下的工作交给迭代器来完成就可以了,并且我们不用关心集合的数据类型,指针总是逐次访问下一地址,直到指针到达终止元素位置。不过其缺点也是显而易见的:不容易指定访问集合中某一个位置的元素。

话不多少,下边给出一个完整的四种访问图像像素方法的示例,并对每种方法的耗时作对比,包括opencv本身的Copy方法;当然五种方法实现的是同一个简单的功能——通过逐个遍历一幅图像的每一个像素,复制该图像到另一Mat对象。


[cpp]  view plain  copy
  1. #include<iostream>  
  2. #include<core/core.hpp>  
  3. #include<highgui/highgui.hpp>  
  4.   
  5. using namespace cv;  
  6. using namespace std;  
  7.   
  8. //At方法  
  9. double CopyImageByAt(Mat originalImage, Mat &targetImage);  
  10. //行指针方法  
  11. double CopyImageByRowPtr(Mat originalImage, Mat &targetImage);  
  12. //指针方法  
  13. double CopyImageByPtr(Mat originalImage, Mat &targetImage);  
  14. //迭代方法  
  15. double CopyImageByIterator(Mat originalImage, Mat &targetImage);  
  16. //Opencv方法  
  17. double CopyFun(Mat originalImage, Mat &targetImage);  
  18.   
  19.   
  20. int main()  
  21. {  
  22.     //读入图片,注意图片路径  
  23.     Mat image=imread("D:\\Picture\\lena.jpg",1);  
  24.   
  25.     //图片读入成功与否判定  
  26.     if(!image.data)  
  27.     {  
  28.         cout<<"you idiot!where did you hide lena!"<<endl;  
  29.         //等待按键  
  30.         system("pause");  
  31.         return -1;  
  32.     }  
  33.     imshow("原始图像",image);  
  34.     //输出图像  
  35.     Mat targetImage(image.size(),image.type());  
  36.     cout<<endl<<"At方法耗时:"<<CopyImageByAt(image,targetImage)<<endl;  
  37.     imshow("At方法",targetImage);  
  38.     cout<<endl<<"行指针方法耗时:"<<CopyImageByRowPtr(image,targetImage)<<endl;  
  39.     imshow("行指针方法",targetImage);  
  40.     cout<<endl<<"指针方法耗时:"<<CopyImageByPtr(image,targetImage)<<endl;  
  41.     imshow("指针方法",targetImage);  
  42.     cout<<endl<<"迭代方法耗时:"<<CopyImageByIterator(image,targetImage)<<endl;  
  43.     imshow("迭代方法",targetImage);  
  44.     cout<<endl<<"OpenCV Copy方法耗时:"<<CopyFun(image,targetImage)<<endl;  
  45.     imshow("Copy方法",targetImage);     
  46.     waitKey();  
  47.     return 0;  
  48. }  
  49.   
  50. //使用at方法实现逐个像素复制  
  51. double CopyImageByAt(Mat originalImage, Mat &targetImage)  
  52. {  
  53.     double now=getTickCount();  
  54.     //行  
  55.     int rows=originalImage.rows;  
  56.     //列  
  57.     int cols=originalImage.cols;  
  58.     for(int i=0;i<rows;i++)  
  59.     {  
  60.         for(int j=0;j<cols;j++)  
  61.         {  
  62.             //若是灰度图像应使用如下表示:   
  63.             //targetImage.at<uchar>(i,j)=originalImage.at<Vec3b>(i,j);            
  64.             targetImage.at<Vec3b>(i,j)[0]=originalImage.at<Vec3b>(i,j)[0];  
  65.             targetImage.at<Vec3b>(i,j)[1]=originalImage.at<Vec3b>(i,j)[1];  
  66.             targetImage.at<Vec3b>(i,j)[2]=originalImage.at<Vec3b>(i,j)[2];  
  67.         }  
  68.     }  
  69.     double end=getTickCount();  
  70.     //返回方法耗时  
  71.     return (end-now)/getTickFrequency();  
  72. }  
  73.   
  74. //使用访问每行首指针方法实现像素复制  
  75. double CopyImageByRowPtr(Mat originalImage, Mat &targetImage)  
  76. {  
  77.     double now=getTickCount();  
  78.     //行  
  79.     int rows=targetImage.rows;  
  80.     //每行总元素数量,此处图像为3通道  
  81.     int totalNum=targetImage.cols*targetImage.channels();  
  82.     for(int i=0;i<rows;i++)  
  83.     {  
  84.         //data1指向目标图像第i行的首元素  
  85.         uchar *data1=targetImage.ptr<uchar>(i);  
  86.         ////data2指向原始图像第i行的首元素  
  87.         uchar *data2=originalImage.ptr<uchar>(i);  
  88.         for(int j=0;j<totalNum;j++)  
  89.         {  
  90.             //遍历每行所有元素  
  91.             data1[j]=data2[j];  
  92.         }  
  93.     }  
  94.     double end=getTickCount();  
  95.     //返回方法耗时  
  96.     return (end-now)/getTickFrequency();  
  97. }  
  98.   
  99. //无扩充的图像,采用指针方法逐个像素复制  
  100. double CopyImageByPtr(Mat originalImage, Mat &targetImage)  
  101. {  
  102.     double now=getTickCount();  
  103.     //行  
  104.     int rows=targetImage.rows;  
  105.     //每行总元素数量,此处图像为3通道  
  106.     int totalNum=targetImage.cols*targetImage.channels();  
  107.     //判断图像数据是否连续  
  108.     if(originalImage.isContinuous())  
  109.     {  
  110.         totalNum*=rows;  
  111.         rows=1;  
  112.     }  
  113.     //外层循环只执行一次  
  114.     for(int i=0;i<rows;i++)  
  115.     {  
  116.         uchar *data1=targetImage.ptr<uchar>(i);  
  117.         uchar *data2=originalImage.ptr<uchar>(i);  
  118.         for(int j=0;j<totalNum;j++)  
  119.         {  
  120.           data1[j]=data2[j];  
  121.         }  
  122.     }  
  123.     double end=getTickCount();  
  124.     //返回方法耗时  
  125.     return (end-now)/getTickFrequency();  
  126. }  
  127.   
  128. //使用迭代器遍历逐个像素复制  
  129. double CopyImageByIterator(Mat originalImage, Mat &targetImage)  
  130. {  
  131.     double now=getTickCount();  
  132.     //获取起始位置迭代器  
  133.     Mat_<Vec3b>::iterator itBegin1=targetImage.begin<Vec3b>();  
  134.     Mat_<Vec3b>::iterator itBegin2=originalImage.begin<Vec3b>();  
  135.     //获取终止位置迭代器  
  136.     Mat_<Vec3b>::iterator itEnd1=targetImage.end<Vec3b>();  
  137.     Mat_<Vec3b>::iterator itEnd2=originalImage.end<Vec3b>();  
  138.     for(;itBegin1!=itEnd1;++itBegin1)  
  139.     {  
  140.         (*itBegin1)[0]=(*itBegin2)[0];  
  141.         (*itBegin1)[1]=(*itBegin2)[1];  
  142.         (*itBegin1)[2]=(*itBegin2)[2];  
  143.         ++itBegin2;  
  144.     }  
  145.     double end=getTickCount();  
  146.     //返回方法耗时  
  147.     return (end-now)/getTickFrequency();  
  148. }  
  149.   
  150. //OpenCV Copy方法实现图像复制  
  151. double CopyFun(Mat originalImage, Mat &targetImage)  
  152. {  
  153.     double now=getTickCount();  
  154.     originalImage.copyTo(targetImage);  
  155.     double end=getTickCount();    
  156.     //返回方法耗时  
  157.     return (end-now)/getTickFrequency();  
  158. }  
### OpenCV 像素坐标系的定义使用 #### 定义 在 OpenCV 中,像素坐标系是以图像的 **左上角** 作为原点 `(0, 0)` 的二维直角坐标系。该坐标系中,`u` 表示横坐标(对应列号),`v` 表示纵坐标(对应行号)。需要注意的是,在 OpenCV 的语境下,通常会将 `u` 替换为 `x`,`v` 替换为 `y` 来表示像素位置[^1]。 具体来说: - 图像矩阵中的每一行代表一个固定的 `v` 或 `y` 值; - 每一列则代表一个固定的 `u` 或 `x` 值。 因此,对于任意像素点 `(x, y)`,可以通过索引 `[y][x]` 访问对应的像素值。这种设计使得图像数据可以被高效存储和操作。 --- #### 使用方法 为了访问或修改图像中的像素值,OpenCV 提供了多种方法: 1. **通过 `.at<T>(row, col)` 方法** 这是最常用的方式之一,适用于不同类型的数据结构。例如,如果图像是三通道 BGR 格式的彩色图像,则可以使用如下代码获取并设置某个像素的颜色值: ```cpp cv::Mat image = cv::imread("example.jpg"); if (!image.empty()) { // 获取指定像素 (100, 50) 处的蓝色分量 uchar blueValue = image.at<cv::Vec3b>(100, 50)[0]; // 修改同一像素处的红色分量 image.at<cv::Vec3b>(100, 50)[2] = 255; } ``` 上述代码片段展示了如何读取和写入单个像素的具体颜色分量。注意这里的参数顺序:第一个参数是行号(即 `y`),第二个参数是列号(即 `x`)[^3]。 2. **利用 `cv::Point` 结构体** 如果更倾向于几何学上的表达形式,也可以借助 `cv::Point(x, y)` 类型来定位像素点的位置。例如: ```cpp cv::Point pt(50, 100); cv::Vec3b& pixel = image.at<cv::Vec3b>(pt); pixel[1] = 128; // 设置绿色分量为中间亮度 ``` 此外,当采用这种方式时需特别留意行列次序的不同映射关系——尽管直观上看似乎应写作 `image.at<Point>(col, row)`,但实际上仍遵循先传入行再传递列的原则。 3. **遍历整个图像** 对整幅画面逐一遍历时可采取双重循环策略完成任务。下面给出一段创建空白画布并将其中部分区域填充成白色的例子: ```cpp int rows = 300, cols = 200; cv::Mat canvas(rows, cols, CV_8UC3, cv::Scalar(0, 0, 0)); // 初始化全黑背景 for(int r=75;r<225;++r){ for(int c=50;c<150;++c){ canvas.at<cv::Vec3b>(r,c)={255,255,255}; // 白色RGB=(255,255,255) } } cv::imshow("Canvas",canvas); cv::waitKey(); ``` 上述实例不仅体现了基本的操作技巧,还进一步巩固了关于坐标轴方向性的认识。 --- ### 总结 综上所述,掌握好 OpenCV像素坐标体系及其实际应用手段至关重要。无论是单独提取某一点的信息还是大规模批量处理多组数据集均离不开这一基础概念的支持。同时也要记得始终按照先行后列这样的固定模式来进行各项运算以免发生错误解读现象的发生。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值