一、本节知识预览

  1、  怎样遍历图像的每一个像素点?

  2、  opencv图像矩阵怎么被存储的?

  3、  怎样衡量我们算法的性能?

  4、  什么是查表,为什么要使用它们?

二、什么是查表,为什么要使用它们?

  假设一张三通道RGB图像,每个像素通道有256种不同颜色取值,那么一个像素点可能有256*256*256(1600多万)种可能颜色取值,这对于实际计算来说,开销是相当大的。而实际计算中,只需要少量的颜色值就能达到相同的效果。常用的一种方法是进行颜色空间缩减。用如下方法,我们可以将颜色空间取值减少10倍:

  然而如果对每个像素点,都应用一次公式减少颜色空间取值,开销仍然很大,因此我们引入一个新方法:查表。

  1. //定义查表
  2. uchar table[256];
  3. int divideWidth = 10;
  4. for (int i = 0;i < 256; ++i)
  5. {
  6. table[i] = (uchar)(divideWidth*(i/divideWidth));
  7. }

  divideWith可以简单理解为取值减少的倍数,例如取值为10,颜色取值由256种可能变成25种。单个像素也只有25*25*25(15625)种可能,较之前1600多万种,计算量极大减少。然后将某个像素点某个通道的值,作为查表的数组索引,可以直接获取到最后的颜色值,避免了数学运算的工作量。

三、怎样衡量我们算法的性能?

  opencv中,我们需要经常衡量一个接口/算法的时间,通过使用Opencv两个自带的函数cv::getTickCount()和cv::getTickFrequency()可以实现,前者记录从系统启动开始CPU计数次数,后者记录CPU计数频率,可用如下代码实现时间衡量:  

  1. double t = (double)getTickCount();
  2.  
  3. // do something ...
  4.  
  5. t = ((double)getTickCount() - t)/getTickFrequency();
  6.  
  7. cout << "Times passed in seconds: " << t << endl;

四、opencv图像矩阵怎么被存储的?

  再来回顾下之前的问题,图像是怎么在内存中被存储的。假设我们的图像是一张n*m的灰度图像,在内存中的存储方式将会是这样的:

  

  如果图像是一张RGB多通道图像,实际在内存中存储是这样的:

  

  可以注意到,通道顺序是BGR而不是原有的RGB。另外由于我们的内存足够大,我们的矩阵可以一行接一行连续被存储,这样可以加快图像扫描的速度,通过cv::Mat::isContinuous()函数确认图像是否被连续存储。

五、怎样遍历图像的每一个像素点?

  一谈到性能,没有什么能比C 风格的[]数组访问操作更高效了,因此可以用如下高效的方式实现查表法减少颜色空间取值:

  1. Mat& ScanImageAndReduceC(Mat& I,const uchar* const table)
  2. {
  3. //accept only char type matrices
  4. CV_Assert(I.depth() == CV_8U);
  5. int channels = I.channels();
  6. int nRows = I.rows;
  7. int nCols = I.cols*channels;
  8. if(I.isContinuous())
  9. {
  10. nCols *= nRows;
  11. nRows = 1;
  12. }
  13.  
  14. int i,j;
  15. uchar *p;
  16. for ( i = 0; i < nRows; ++i)
  17. {
  18. p = I.ptr<uchar>(i);
  19. for(j = 0;j < nCols;++j)
  20. {
  21. p[j] = table[p[j]];
  22. }
  23. }
  24. return I;
  25. }

  此外,我们还可以通过opencv提供的递归方法实现图像的遍历:

  1. Mat& ScanImageAndReduceIterator(Mat& I,const uchar* const table)
  2. {
  3. CV_Assert(I.depth() == CV_8U);
  4. const int channels = I.channels();
  5. switch(channels)
  6. {
  7. case 1:
  8. {
  9. MatIterator_<uchar> it,end;
  10. for( it = I.begin<uchar>(),end = I.end<uchar>();it != end;++it)
  11. {
  12. *it = table[*it];
  13. }
  14. break;
  15. }
  16. case 3:
  17. {
  18. MatIterator_<Vec3b> it,end;
  19. for(it = I.begin<Vec3b>(),end = I.end<Vec3b>();it != end;++it)
  20. {
  21. (*it)[0] = table[(*it)[0]];
  22. (*it)[1] = table[(*it)[1]];
  23. (*it)[2] = table[(*it)[2]];
  24. }
  25. break;
  26. }
  27. }
  28. return I;
  29. }

  同时,还可以使用at方法实时计算图像坐标实现图像的遍历,新定义Mat_<Vec3b> _I是为了编码偷懒的方式,可以直接使用()运算符而不是at函数:

  1. Mat& ScanImageAndReduceRandomAccess(Mat& I,const uchar * const table)
  2. {
  3. CV_Assert(I.depth() == CV_8U);
  4. const int channels = I.channels();
  5. switch(channels)
  6. {
  7. case 1:
  8. {
  9. for (int i = 0;i < I.rows;++i)
  10. for (int j = 0; j < I.cols; ++j)
  11. {
  12. I.at<uchar>(i,j) = table[I.at<uchar>(i,j)];
  13. }
  14. break;
  15. }
  16. case 3:
  17. {
  18. Mat_<Vec3b> _I = I;
  19. for (int i = 0;i < I.rows; ++i)
  20. for (int j = 0;j < I.cols; ++j)
  21. {
  22. //_I.at<Vec3b>(i,j)[0] = table[_I.at<Vec3b>(i,j)[0]];
  23. //_I.at<Vec3b>(i,j)[1] = table[_I.at<Vec3b>(i,j)[1]];
  24. //_I.at<Vec3b>(i,j)[2] = table[_I.at<Vec3b>(i,j)[2]];
  25. _I(i,j)[0] = table[_I(i,j)[0]];
  26. _I(i,j)[1] = table[_I(i,j)[1]];
  27. _I(i,j)[2] = table[_I(i,j)[2]];
  28. }
  29. I = _I;
  30. break;
  31. }
  32. }
  33. return I;
  34. }

  OpenCV库也为我们提供一个快速查表的库函数:

  1. Mat lookUpTable(1, 256, CV_8U);
  2. uchar* p = lookUpTable.ptr();
  3. for( int i = 0; i < 256; ++i)
  4. p[i] = table[i];
  5. LUT(I, lookUpTable, J);

  最后,我们附上整个程序源码,通过调用摄像头,获取图像,然后对前100帧图像利用查表法进行颜色空间缩减:

  1. #include<opencv2/opencv.hpp>
  2. #include<cv.h>
  3.  
  4. using namespace cv;
  5. using namespace std;
  6.  
  7. Mat& ScanImageAndReduceC(Mat& I,const uchar* const table)
  8. {
  9. //accept only char type matrices
  10. CV_Assert(I.depth() == CV_8U);
  11. int channels = I.channels();
  12. int nRows = I.rows;
  13. int nCols = I.cols*channels;
  14. if(I.isContinuous())
  15. {
  16. nCols *= nRows;
  17. nRows = 1;
  18. }
  19.  
  20. int i,j;
  21. uchar *p;
  22. for ( i = 0; i < nRows; ++i)
  23. {
  24. p = I.ptr<uchar>(i);
  25. for(j = 0;j < nCols;++j)
  26. {
  27. p[j] = table[p[j]];
  28. }
  29. }
  30. return I;
  31. }
  32.  
  33. Mat& ScanImageAndReduceIterator(Mat& I,const uchar* const table)
  34. {
  35. CV_Assert(I.depth() == CV_8U);
  36. const int channels = I.channels();
  37. switch(channels)
  38. {
  39. case 1:
  40. {
  41. MatIterator_<uchar> it,end;
  42. for( it = I.begin<uchar>(),end = I.end<uchar>();it != end;++it)
  43. {
  44. *it = table[*it];
  45. }
  46. break;
  47. }
  48. case 3:
  49. {
  50. MatIterator_<Vec3b> it,end;
  51. for(it = I.begin<Vec3b>(),end = I.end<Vec3b>();it != end;++it)
  52. {
  53. (*it)[0] = table[(*it)[0]];
  54. (*it)[1] = table[(*it)[1]];
  55. (*it)[2] = table[(*it)[2]];
  56. }
  57. break;
  58. }
  59. }
  60. return I;
  61. }
  62.  
  63. Mat& ScanImageAndReduceRandomAccess(Mat& I,const uchar * const table)
  64. {
  65. CV_Assert(I.depth() == CV_8U);
  66. const int channels = I.channels();
  67. switch(channels)
  68. {
  69. case 1:
  70. {
  71. for (int i = 0;i < I.rows;++i)
  72. for (int j = 0; j < I.cols; ++j)
  73. {
  74. I.at<uchar>(i,j) = table[I.at<uchar>(i,j)];
  75. }
  76. break;
  77. }
  78. case 3:
  79. {
  80. Mat_<Vec3b> _I = I;
  81. for (int i = 0;i < I.rows; ++i)
  82. for (int j = 0;j < I.cols; ++j)
  83. {
  84. //_I.at<Vec3b>(i,j)[0] = table[_I.at<Vec3b>(i,j)[0]];
  85. //_I.at<Vec3b>(i,j)[1] = table[_I.at<Vec3b>(i,j)[1]];
  86. //_I.at<Vec3b>(i,j)[2] = table[_I.at<Vec3b>(i,j)[2]];
  87. _I(i,j)[0] = table[_I(i,j)[0]];
  88. _I(i,j)[1] = table[_I(i,j)[1]];
  89. _I(i,j)[2] = table[_I(i,j)[2]];
  90. }
  91. I = _I;
  92. break;
  93. }
  94. }
  95. return I;
  96. }
  97.  
  98. Mat& ScanImageAndReduceLut(Mat& I,Mat& J,const uchar * const table)
  99. {
  100. Mat lookUpTable(1,256,CV_8U);
  101. uchar* p = lookUpTable.ptr();
  102. for ( int i = 0;i < 256; ++i)
  103. p[i] = table[i];
  104. LUT(I,lookUpTable,J);
  105. return J;
  106. }
  107.  
  108. int main( )
  109. {
  110. Mat frame_input,frame_src,frame_reduce_c,frame_reduce_iterator,frame_reduce_random_access,frame_reduce_lut;
  111. VideoCapture capture(0);
  112. if(capture.isOpened())
  113. {
  114. printf("打开摄像头成功\n");
  115. capture >> frame_input;
  116. printf("图像分辨率为:%d * %d,通道数为%d\n",frame_input.rows,frame_input.cols,frame_input.channels());
  117. }
  118.  
  119. //定义查表
  120. uchar table[256];
  121. int divideWidth = 30;
  122. for (int i = 0;i < 256; ++i)
  123. {
  124. table[i] = (uchar)(divideWidth*(i/divideWidth));
  125. }
  126.  
  127. float time_cnts_c = 0,time_cnts_iterator = 0,time_cnts_random_access = 0,time_cnts_lut = 0;
  128. double tick = 0,number = 0;
  129.  
  130. while(number < 100){
  131.  
  132. ++number;
  133. printf("读取第%f帧图像\n",number);
  134.  
  135. capture >> frame_input;
  136. if(frame_input.empty()){
  137. printf("--(!) No captured frame -- Break!");
  138. }
  139. else{
  140.  
  141. frame_src = frame_input.clone();
  142. frame_reduce_c = frame_input.clone();
  143. frame_reduce_iterator = frame_input.clone();
  144. frame_reduce_random_access = frame_input.clone();
  145.  
  146. tick = getTickCount();
  147. ScanImageAndReduceC(frame_reduce_c,table);
  148. time_cnts_c += ((double)getTickCount()- tick)*1000 / getTickFrequency();
  149.  
  150. tick = getTickCount();
  151. ScanImageAndReduceIterator(frame_reduce_iterator,table);
  152. time_cnts_iterator += ((double)getTickCount()- tick)*1000 / getTickFrequency();
  153.  
  154. tick = getTickCount();
  155. ScanImageAndReduceRandomAccess(frame_reduce_random_access,table);
  156. time_cnts_random_access += ((double)getTickCount()- tick)*1000 / getTickFrequency();
  157.  
  158. tick = getTickCount();
  159. ScanImageAndReduceLut(frame_src,frame_reduce_lut,table);
  160. time_cnts_lut += ((double)getTickCount()- tick)*1000 / getTickFrequency();
  161.  
  162. imshow("原始图像", frame_src);
  163. imshow("ScanImageAndReduceC",frame_reduce_c);
  164. imshow("ScanImageAndReduceIterator",frame_reduce_iterator);
  165. imshow("ScanImageAndReduceRandomAccess",frame_reduce_random_access);
  166. imshow("ScanImageAndReduceLut",frame_reduce_lut);
  167.  
  168. }
  169. waitKey(10);
  170. }
  171.  
  172. printf("time_cnts_c:%f\n",time_cnts_c/100);
  173. printf("time_cnts_iterator:%f\n",time_cnts_iterator/100);
  174. printf("time_cnts_random_access:%f\n",time_cnts_random_access/100);
  175. printf("time_cnts_lut:%f\n",time_cnts_lut/100);
  176.  
  177. waitKey(1000000);
  178. return 0;
  179. }

六、实验结果

  opencv教程给出的时间参考如下:

  https://docs.opencv.org/master/db/da5/tutorial_how_to_scan_images.html

Method

Time

Efficient Way

79.4717 milliseconds

Iterator

83.7201 milliseconds

On-The-Fly RA

93.7878 milliseconds

LUT function

32.5759 milliseconds

  实际在我们环境上(480*640,3通道)测试的结果如下:

Method

Time

Efficient Way

4.605026 milliseconds

Iterator

92.846123 milliseconds

On-The-Fly RA

240.321487 milliseconds

LUT function

3.741437 milliseconds

  实验结果表明,使用opencv自带的LUT函数,效率最高。这是因为OpenCV内建的多线程原因。其次是c语言高效的[]数组访问方式。

day-15 用opencv怎么扫描图像,利用查找表和计时的更多相关文章

  1. OpenCV学习笔记:如何扫描图像、利用查找表和计时

    目的 我们将探索以下问题的答案: 如何遍历图像中的每一个像素? OpenCV的矩阵值是如何存储的? 如何测试我们所实现算法的性能? 查找表是什么?为什么要用它? 测试用例 这里我们测试的,是一种简单的 ...

  2. 1.2OpenCV如何扫描图像,利用查找表和计时

    查找表 颜色缩减法:如果矩阵元素存储的是单通道像素,使用C或C++的无符号字符类型,那么像素可有256个不同值. 但若是三通道图像,这种存储格式的颜色数就太多了(确切地说,有一千六百多万种).用如此之 ...

  3. OpenCV从入门到放弃系列之——如何扫描图像、利用查找表和计时

    目的 如何遍历图像中的每一个像素? OpenCV的矩阵值是如何存储的? 如何测试我们所实现算法的性能? 查找表是什么?为什么要用它? 测试用例 颜色空间缩减.具体做法就是:将现有颜色空间值除以某个输入 ...

  4. 第三节,使用OpenCV 3处理图像(模糊滤波、边缘检测)

    一 不同色彩空间的转换 OpenCV中有数百种关于在不同色彩空间之间转换的方法.当前,在计算机中有三种常用的色彩空间:灰度,BGR以及HSV(Hue,Saturation,Value). 灰度色彩空间 ...

  5. 从 TWAIN 设备中扫描图像

    转自(http://yonsm.net/scan-images-from-a-twain-device/) 一.简介 TWAIN 数据源管理程序 (DSM) 工业标准的软件库,用于从静态图像设备提取图 ...

  6. [OpenCV实战]46 在OpenCV下应用图像强度变换实现图像对比度均衡

    本文主要介绍基于图像强度变换算法来实现图像对比度均衡.通过图像对比度均衡能够抑制图像中的无效信息,使图像转换为更符合计算机或人处理分析的形式,以提高图像的视觉价值和使用价值.本文主要通过OpenCV ...

  7. opencv提取截获图像(总结摘来)

    opencv提取截获图像(总结摘来) http://blog.csdn.net/wuxiaoyao12/article/details/7305865 版权声明:本文为博主原创文章,未经博主允许不得转 ...

  8. metasploit常用服务扫描和利用模块

    metasploit常用服务扫描和利用模块 SMB扫描 smb枚举auxiliary/scanner/smb/smb_enumusers 扫描命名管道auxiliary/scanner/smb/pip ...

  9. 14.使用Crunch创建字典----Armitage扫描和利用----设置虚拟渗透测试实验室----proxychains最大匿名

    使用Crunch创建字典 kali自带的字典 usr/share/wordlists cd Desktop mkdir wordlists cd wordlists/ crunch --help cr ...

随机推荐

  1. oracle之DQL

    一.单表查询 语法:select * from table where 条件 group by 分组 having 过滤分组 order by 排序 --查询平均工资低于2000的部门的最大工资和平均 ...

  2. iOS:网络请求(17-12-26更)

    先开一篇,以后再补充... 1.判断请求数据是否为空. 参考简书<解决NSNull对象的烦恼> --ma772528138 之前使用网络请求没丢包,而使用 socket 的时候丢过包.如果 ...

  3. python 输入一个整数,判断其是否既是3的倍数,又是5的倍数

    v = int(input('请输入一个整数:')) if v % 3 == 0 and v % 5 ==0: print(v,'即是3的倍数又是5的倍数') else: print('不是3或5的倍 ...

  4. 走进MySQL

    MySQL 是最流行的关系型数据库管理系统,在 WEB 应用方面 MySQL 是最好的 RDBMS(Relational Database Management System:关系数据库管理系统)应用 ...

  5. ES6 开发规范-最佳实践

    ES6 开发规范(最佳实践) 本文为开发规范,收集方便日后查看. [开发规范]https://blog.csdn.net/zzzkk2009/article/details/53171058?utm_ ...

  6. php源码建博客1--搭建站点-实现登录页面

    主要: 站点搭建 实现登录页面 分析及改进 站点搭建 1)  在apache安装目录下: [conf\extra\httpd-vhosts.conf]加入站点配置 <VirtualHost *: ...

  7. 插入排序,C语言实现

    插入排序是稳定排序,时间复杂度最低为O(n),最高为O(n^2),平均为O(n^2). 插入排序是将数组分为两部分,一部分已经排好序,另一部分未排好序,每次从未排好序的部分取第一个元素插入到已经排好序 ...

  8. R语言爬虫:爬取包含所有R包的名称及介绍

    第一种方法 library("rvest") page <- read_html("https://cran.rstudio.com/web/packages/av ...

  9. Java基础之static关键字的用法

    Java中的static关键字主要用于内存管理.我们可以应用static关键字在变量.方法.块和嵌套类中. static关键字属于类,而不是类的实例.        静态(static)可以是: 变量 ...

  10. P1294 高手去散步

    P1294 高手去散步 题目背景 高手最近谈恋爱了.不过是单相思.“即使是单相思,也是完整的爱情”,高手从未放弃对它的追求.今天,这个阳光明媚的早晨,太阳从西边缓缓升起.于是它找到高手,希望在晨读开始 ...