乞丐哥的私房菜(Windows OpenCV篇——Core Function 节 之 图像扫描、性能测试)二

  • 操作系统:Windows 7 SP1
  • 编译器:mingw64 g++ 8.1.0
  • 编辑器:Emacs 30.1
  • 第三方库:OpenCV 4.1.2.0
  • 对 Emacs 有基本了解

目标

  • 浏览图像的每个像素
  • OpenCV 矩阵值存储
  • 查找表

测试用例

  • 减色方法。有时用最少的颜色可以得到相同的结果
  • 例如:0-9 之间取 0,10-19 之间取 10,以此类推
  • 当将 uchar 除以一个整数,任何小数向下舍入,结果也将是 char,因此:
    I n e w = ( I o l d 10 ) ∗ 10 I_{new} = (\frac{I_{old}} {10}) * 10 Inew=(10Iold)10
  • 简单的颜色空间降维算法应用此公式
  • 对于大图像,事先做好所有可能的值,并在任务期间查找表(查找表是简单的数组,具有一个或多个维度),优点是不需要计算
  • 下面的示例将:
    • 读取命令行参数传递的图像(可以是彩色或灰度图像)
    • 根据命令行参数的整数值应用图像缩减
    • 在 OpenCV 中,目前有三种主要的方法逐像素遍历图像
    • 使用每种方法对图像进行扫描并打印出所需的时间
    • 源码在这里下载
        how_to_scan_images imageName.jpg intValueToReduce [G]
      
    • 最后的参数是可选的,如果提供,以灰度格式加载,否则将使用 BGR 颜色空间
        int divideWith = 0;
        stringstream s;
        s << argv[2];
        s >> divideWith;
        if (!s || ! divideWith) {
          cout << "输入的除数无效!" << endl;
          return -1;
         }
        uchar table[256];
        for (int i = 0; i < 256; ++i)
          table[i] = (uchar) (i / divideWith * (i / divideWith ));
      
    • 使用 C++ stringstream 将命令行的第三个参数从文本转换为整数格式,这没有 OpenCV 特定代码
    • OpenCV 提供了两简单的函数实现时间(性能)的测量
        double t = (double) getTickCount();
        // do something ...
        t = ((double)getTickCount() - t) / getTickFrequency();
        cout << "Times passed in seconds: " << t << endl;
      
      • cv::getTickCount() 从某个事件返回 CPU 的脉冲数
      • cv::getTickFrequency() 返回 CPU 在一秒内发出多少脉冲

图像矩阵如何存储在内存中?

Mat-图像容器中说过,矩阵的大小取决于使用的颜色系统。准确地说取决于使用的通道数

  • 灰度
    在这里插入图片描述

  • 多通道
    在这里插入图片描述

这是 BGR 而不是 RGB.很多情况下内存足够大,可以按顺序存储行,行可能会一个接一个地排列,形成一个长行。
这可能有助于加快扫描过程。可通过 cv::Mat::isContinuous() 函数来询问矩阵是否是这种情况

高效的方法

当涉及到性能时,没有比经典的 C 风格操作符访问更好的方法了。推荐最有效的方法是:

  Mat& ScanImageAndReduceC(Mat& I, const uchar* const table) {
    // Accept only char type matrices
    CV_Assert(I.depth() == CV_8U);
    int channels = I.channels();
    int nRows = I.rows;
    int nCols = I.cols * channels;
    if (I.isContinuous()) {
      nCols *= nRows;
      nRows = 1;
    }
    int i, j;
    uchar* p;
    for (i = 0; i < nRows; ++i){
      p = I.ptr<uchar>(i);
      for (j = 0; j < nCols; ++j){
        p[j] = table[p[j]];
      }
    }
    return I;
  }
  • 在这里,只是获取每行的起始指针,并逐行遍历直到结束。在矩阵以连续方式存储的情况下,只需请求一次指针并一
    直遍历到结束。彩色图像有三个通道,是普通情况的三倍
  • 另一种方法,先判断图像是否加载完成。如果是灰度图像:
      uchar* p = I.data;
      for( unsigned int i = 0; i < ncol*nrows; ++i)
        ,*p++ = table[*p];
    

(安全)迭代方法

只需要询问图像矩阵的开始和结束,使迭代器指向值,用*运算符

  Mat& ScanImageAndReduceIterator(Mat& I, const uchar* const table) {
    // accept only char type matrices
    CV_Assert(I.depth() == CV_8U);

    const int channels = I.channels();
    switch(channels)
      {
      case 1:
        {
  	MatIterator_<uchar> it, end;
  	for( it = I.begin<uchar>(), end = I.end<uchar>(); it != end; ++it)
  	  ,*it = table[*it];
  	break;
        }
      case 3:
        {
  	MatIterator_<Vec3b> it, end;
  	for( it = I.begin<Vec3b>(), end = I.end<Vec3b>(); it != end; ++it)
  	  {
  	    (*it)[0] = table[(*it)[0]];
  	    (*it)[1] = table[(*it)[1]];
  	    (*it)[2] = table[(*it)[2]];
  	  }
        }
      }

    return I;
  }

需要记住,OpenCV 的迭代器会遍历列,并自动跳到下一行

动态地址计算与引用返回

  Mat& ScanImageAndReduceRandomAccess(Mat& I, const uchar* const table) {
    // accept only char type matrices
    CV_Assert(I.depth() == CV_8U);
    const int channels = I.channels();
    switch(channels)
      {
      case 1:
        {
  	for( int i = 0; i < I.rows; ++i)
  	  for( int j = 0; j < I.cols; ++j )
  	    I.at<uchar>(i,j) = table[I.at<uchar>(i,j)];
  	break;
        }
      case 3:
        {
  	Mat_<Vec3b> _I = I;
  	for( int i = 0; i < I.rows; ++i)
  	  for( int j = 0; j < I.cols; ++j )
  	    {
  	      _I(i,j)[0] = table[_I(i,j)[0]];
  	      _I(i,j)[1] = table[_I(i,j)[1]];
  	      _I(i,j)[2] = table[_I(i,j)[2]];
              }
  	I = _I;
  	break;
        }
      }
    return I;
  }

如果对图像进行多次查找,使用这种方法可能会很麻烦

  • 核心功能
    在实际中,可能希望将给定图像的所有值修改为其他值。OpenCV 提供了一个函数来修改图像值,无需编写图像扫描逻辑
    使用核心模块的 cv::LUT() 函数
  Mat lookUpTable(1, 256, CV_8U);
  uchar* p = lookUpTable.ptr();
  for( int i = 0; i < 256; ++i)
      p[i] = table[i];
#+END_SRC
#+BEGIN_SRC C
  LUT(I, lookUpTable, J); // I 为输入图像,J 为输出图像

性能差异

使用的图像大小为(2560 x 1600 ),函数调用一百次得到的结果取平均值

MethodTime
Efficient Way79.4717 milliseconds
Iterator83.7201 milliseconds
On-The-Fly RA93.7878 milliseconds
LUT function32.5759 milliseconds

结论

  • 尽可能的用 OpenCV 提供的方法
  • 最快的方法是 LUT 函数
  • OpenCV 库通过 Intel 的线程构建块(Threaded Building Blocks)启用多线程
  • 在实际中,真要手写请自己斟酌:指针方法、迭代器安全法、带引用返回的即时地址计算
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值