refs: https://docs.opencv.org/4.3.0/db/da5/tutorial_how_to_scan_images.html

目录

Goal

Our test case:a color space reduction algorithm

how do we measure time?

How is the image matrix stored in memory?

Performance Difference


Goal

We'll seek answers for the following questions:

  • How to go through each and every pixel of an image?
  • How are OpenCV matrix values stored?
  • How to measure the performance of our algorithm?
  • What are lookup tables and why use them?

Our test case:a color space reduction algorithm

Let us consider a simple color reduction method. By using the unsigned char C and C++ type for matrix item storing, a channel of pixel may have up to 256 different values. For a three channel image this can allow the formation of way too many colors (16 million to be exact). Working with so many color shades may give a heavy blow to our algorithm performance. However, sometimes it is enough to work with a lot less of them to get the same final result.

In this cases it's common that we make a color space reduction. This means that we divide the color space current value with a new input value to end up with fewer colors. For instance every value between zero and nine takes the new value zero, every value between ten and nineteen the value ten and so on.

When you divide an uchar (unsigned char - aka values between zero and 255) value with an int value the result will be also char. These values may only be char values. Therefore, any fraction will be rounded down. Taking advantage of this fact the upper operation in the uchar domain may be expressed as:

A simple color space reduction algorithm would consist of just passing through every pixel of an image matrix and applying this formula. It's worth noting that we do a divide and a multiplication operation. These operations are bloody expensive for a system.

how do we measure time?

Well OpenCV offers two simple functions to achieve this cv::getTickCount() and cv::getTickFrequency() . The first returns the number of ticks of your systems CPU from a certain event (like since you booted your system). The second returns how many times your CPU emits a tick during a second. So, measuring amount of time elapsed between two operations is as easy as:

double t = (double)getTickCount();
// do something ...
t = 1000* ((double)getTickCount() - t)/getTickFrequency();
cout << "Times passed in seconds: " << t << endl;

C中可以使用

       clock_t startTime = clock();//  doing somethingclock_t endTime = clock();std::cout <<  << double(endTime-startTime) / CLOCKS_PER_SEC << " s " << std::endl;

How is the image matrix stored in memory?

As you could already read in my Mat - The Basic Image Container tutorial the size of the matrix depends on the color system used. More accurately, it depends on the number of channels used. In case of a grayscale image we have something like:

For multichannel images the columns contain as many sub columns as the number of channels. For example in case of an BGR color system:

Note that the order of the channels is inverse: BGR instead of RGB.

everything is in a single place following one after another this may help to speed up the scanning process.

#include <iostream>
#include <opencv.hpp>
#include <ctime>using namespace cv;
using namespace std;uchar* buildTable(const int divideWith)
{uchar table[256];for (int i = 0; i < 256; ++i)table[i] = (uchar)(divideWith * (i / divideWith));return table;
}//The efficient way
//When it comes to performance you cannot beat the classic C style operator[] (pointer) access.
//Therefore, the most efficient method we can recommend for making the assignment is :
Mat& ScanImageAndReduceC(Mat& I, const uchar* const table)
{// accept only char type matricesCV_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]];//  }//}Here we basically just acquire a pointer to the start of each row and go through it until it ends.In the special case that the matrix is stored in a continuous manner we only need to request the pointer a single time and go all the way to the end.We need to look out for color images : we have three channels so we need to pass through three times more items in each row.There's another way of this. The data data member of a Mat object returns the pointer to the first row, first column. If this pointer is null you have no valid input in that object. Checking this is the simplest method to check if your image loading was a success.In case the storage is continuous we can use this to go through the whole data pointer. //   //You would get the same result.However, this code is a lot harder to read later onuchar* p = I.data;for (unsigned int i = 0; i < nCols*nRows; ++i)*p++ = table[*p];return I;
}The iterator(safe) method
In case of the efficient way making sure that you pass through the right amount of uchar fields and to skip the gaps that may occur
between the rows was your responsibility.The iterator method is considered a safer way as it takes over these tasks from the user.
All you need to do is to ask the begin and the end of the image matrix and then just increase the begin iterator until you reach the end.
To acquire the value pointed by the iterator use the * operator (add it before it).
Mat& ScanImageAndReduceIterator(Mat& I, const uchar* const table)
{// accept only char type matricesCV_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;
}On - the - fly address calculation with reference returning
The final method isn't recommended for scanning. It was made to acquire or modify somehow random elements in the image.
Its basic usage is to specify the row and column number of the item you want to access.
During our earlier scanning methods you could already notice that it is important through what type we are looking at the image.
It's no different here as you need to manually specify what type to use at the automatic lookup.
Mat& ScanImageAndReduceRandomAccess(Mat& I, const uchar* const table)
{// accept only char type matricesCV_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;
}
//The function takes your input type and coordinates and calculates the address of the queried item.
//Then returns a reference to that.This may be a constant when you get the value and non - constant when you set the valuevoid main()
{Mat image_grass = imread("Grass.jpg", IMREAD_COLOR);//Mat image_grass = imread("Grass.jpg", IMREAD_GRAYSCALE);if (!image_grass.data){printf("No data\n");return;}pyrDown(image_grass, image_grass, Size(image_grass.cols / 2, image_grass.rows / 2));imshow("grass", image_grass);int testNum = 100;Mat efficientWay = image_grass.clone();double t_e = (double)getTickCount();Mat efficientWay_grass = ScanImageAndReduceC(efficientWay, buildTable(testNum));t_e = ((double)getTickCount() - t_e) / getTickFrequency() ;cout << "efficientWay " << t_e << endl;imshow("efficientWay reduce grass", efficientWay_grass);Mat safeWay = image_grass.clone();//imshow("before safe", safeWay);double t_s = (double)getTickCount();Mat safeWay_grass = ScanImageAndReduceIterator(safeWay, buildTable(testNum));t_s = ((double)getTickCount() - t_s) / getTickFrequency();cout << "sfaeWay " << t_s << "s" << endl;imshow("safeWay reduce grass", safeWay_grass);Mat onTheFly = image_grass.clone();double t_o = (double)getTickCount();Mat onTheFly_grass = ScanImageAndReduceRandomAccess(onTheFly, buildTable(testNum));t_o = ((double)getTickCount() - t_o) / getTickFrequency();cout << "onTheFly " << t_o << endl;imshow("onTheFly grass", onTheFly_grass);Mat lut = image_grass.clone();Mat lut_grass;This is a bonus method of achieving lookup table modification in an image.In image processing it's quite common that you want to modify all of a given image values to some other value. OpenCV provides a function for modifying image values, without the need to write the scanning logic of the image. We use the cv::LUT() function of the core module. First we build a Mat type of the lookup table:Mat lookUpTable(1, 256, CV_8U);uchar* p = lookUpTable.ptr();for (int i = 0; i < 256; ++i)p[i] = buildTable(testNum)[i];//clock_t startTime1 = clock();//LUT(lut, lookUpTable, lut_grass);//clock_t endTime1 = clock();//cout << "LUT Way " << double(endTime1 - startTime1) / CLOCKS_PER_SEC << "s" << endl;double t_l = (double)getTickCount();LUT(lut, lookUpTable, lut_grass);t_l = ((double)getTickCount() - t_l) / getTickFrequency();cout << "LUT Way " << t_l << "s" << endl;imshow("lut grass", lut_grass);waitKey();return;
}

官方文档

#include <opencv2/core.hpp>
#include <opencv2/core/utility.hpp>
#include "opencv2/imgcodecs.hpp"
#include <opencv2/highgui.hpp>
#include <iostream>
#include <sstream>using namespace std;
using namespace cv;static void help()
{cout<< "\n--------------------------------------------------------------------------" << endl<< "This program shows how to scan image objects in OpenCV (cv::Mat). As use case"<< " we take an input image and divide the native color palette (255) with the "  << endl<< "input. Shows C operator[] method, iterators and at function for on-the-fly item address calculation."<< endl<< "Usage:"                                                                       << endl<< "./how_to_scan_images <imageNameToUse> <divideWith> [G]"                       << endl<< "if you add a G parameter the image is processed in gray scale"                << endl<< "--------------------------------------------------------------------------"   << endl<< endl;
}Mat& ScanImageAndReduceC(Mat& I, const uchar* table);
Mat& ScanImageAndReduceIterator(Mat& I, const uchar* table);
Mat& ScanImageAndReduceRandomAccess(Mat& I, const uchar * table);int main( int argc, char* argv[])
{help();if (argc < 3){cout << "Not enough parameters" << endl;return -1;}Mat I, J;if( argc == 4 && !strcmp(argv[3],"G") )I = imread(argv[1], IMREAD_GRAYSCALE);elseI = imread(argv[1], IMREAD_COLOR);if (I.empty()){cout << "The image" << argv[1] << " could not be loaded." << endl;return -1;}//! [dividewith]int divideWith = 0; // convert our input string to number - C++ stylestringstream s;s << argv[2];s >> divideWith;if (!s || !divideWith){cout << "Invalid number entered for dividing. " << endl;return -1;}uchar table[256];for (int i = 0; i < 256; ++i)table[i] = (uchar)(divideWith * (i/divideWith));//! [dividewith]const int times = 100;double t;t = (double)getTickCount();for (int i = 0; i < times; ++i){cv::Mat clone_i = I.clone();J = ScanImageAndReduceC(clone_i, table);}t = 1000*((double)getTickCount() - t)/getTickFrequency();t /= times;cout << "Time of reducing with the C operator [] (averaged for "<< times << " runs): " << t << " milliseconds."<< endl;t = (double)getTickCount();for (int i = 0; i < times; ++i){cv::Mat clone_i = I.clone();J = ScanImageAndReduceIterator(clone_i, table);}t = 1000*((double)getTickCount() - t)/getTickFrequency();t /= times;cout << "Time of reducing with the iterator (averaged for "<< times << " runs): " << t << " milliseconds."<< endl;t = (double)getTickCount();for (int i = 0; i < times; ++i){cv::Mat clone_i = I.clone();ScanImageAndReduceRandomAccess(clone_i, table);}t = 1000*((double)getTickCount() - t)/getTickFrequency();t /= times;cout << "Time of reducing with the on-the-fly address generation - at function (averaged for "<< times << " runs): " << t << " milliseconds."<< endl;//! [table-init]Mat lookUpTable(1, 256, CV_8U);uchar* p = lookUpTable.ptr();for( int i = 0; i < 256; ++i)p[i] = table[i];//! [table-init]t = (double)getTickCount();for (int i = 0; i < times; ++i)//! [table-use]LUT(I, lookUpTable, J);//! [table-use]t = 1000*((double)getTickCount() - t)/getTickFrequency();t /= times;cout << "Time of reducing with the LUT function (averaged for "<< times << " runs): " << t << " milliseconds."<< endl;return 0;
}//! [scan-c]
Mat& ScanImageAndReduceC(Mat& I, const uchar* const table)
{// accept only char type matricesCV_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;
}
//! [scan-c]//! [scan-iterator]
Mat& ScanImageAndReduceIterator(Mat& I, const uchar* const table)
{// accept only char type matricesCV_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;
}
//! [scan-iterator]//! [scan-random]
Mat& ScanImageAndReduceRandomAccess(Mat& I, const uchar* const table)
{// accept only char type matricesCV_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;
}
//! [scan-random]

Performance Difference

We can conclude a couple of things. If possible, use the already made functions of OpenCV (instead of reinventing these). The fastest method turns out to be the LUT function. This is because the OpenCV library is multi-thread enabled via Intel Threaded Building Blocks. However, if you need to write a simple image scan prefer the pointer method. The iterator is a safer bet, however quite slower. Using the on-the-fly reference access method for full image scan is the most costly in debug mode. In the release mode it may beat the iterator approach or not, however it surely sacrifices for this the safety trait of iterators.

Improving Opencv 2:The Core Functionality :How to scan images, lookup tables相关推荐

  1. opencv回顾之Core module

    该篇围绕Core Functionality模块进行展开 该模块的主要作用是成为构建opencv更多高级功能的基础核心层. Mat基础图像存储数据结构 将Mat对象赋值给其他Mat变量将会共享一个地址 ...

  2. OpenCV 之 Mat 类

    数字图像可看作一个数值矩阵, 其中的每个元素代表一个像素点,如下图所示: OpenCV 中,用 Mat 来表示该数值矩阵,它是很关键的一种数据结构,因为 OpenCV 中的大部分函数都和 Mat 有关 ...

  3. 使用 OpenCV4 和 C++ 构建计算机视觉项目:1~5

    原文:Building Computer Vision Projects with OpenCV 4 and C++ 协议:CC BY-NC-SA 4.0 译者:飞龙 本文来自[ApacheCN 计算 ...

  4. OpenCV 1.x 2.x 编程简介(矩阵/图像/视频的基本读写操作)

    OpenCV 编程简介(矩阵/图像/视频的基本读写操作) Introduction to programming with OpenCV OpenCV编程简介 作者: Gady Agam Depart ...

  5. OpenCV 编程简单介绍(矩阵/图像/视频的基本读写操作)

    PS. 因为csdn博客文章长度有限制,本文有部分内容被截掉了. 在OpenCV中文站点的wiki上有可读性更好.而且是完整的版本号,欢迎浏览. OpenCV Wiki :<OpenCV 编程简 ...

  6. 绘制 polygons and polylines:OpenCV版本

    OpenCV documentation index - OpenCV 文档索引 https://www.docs.opencv.org/ master (4.x) https://www.docs. ...

  7. OpenCV在线文档目录翻译(一)

    英文地址:http://docs.opencv.org/ 对OpenCV的Online Documentation 目录文档进行了翻译整理. OpenCV API Reference 目录:Conte ...

  8. OpenCV基础(基于Opencv4.4+VS2019)

    OpenCV基础(基于Opencv4.4+VS2019) 1.OpenCV介绍 OpenCV是计算机视觉开源库,主要算法涉及图像处理和机器学习相关方法. 是Intel公司贡献出来的,俄罗斯工程师贡献大 ...

  9. OpenCV 3 drawing rectangle - 绘制 rectangle

    OpenCV 3 drawing rectangle - 绘制 rectangle OpenCV documentation index - OpenCV 文档索引 https://www.docs. ...

  10. OpenCV(2)--OpenCV介绍

    文章目录结构 1. 简介 2. cv命名空间 3. 自动的内存管理 4. 数据输出的自动分配 5. 饱和算法(Saturation Arithmetics) 6. 固定的像素类型和对模板的限制使用 7 ...

最新文章

  1. 马云打响本地生活消费攻坚战,饿了么获手淘一级入口,美团危险了
  2. 小白都能看懂最小生成树prime算法
  3. IOS设计模式第六篇之适配器设计模式
  4. php 多选的 二进制,PHP二进制操作初体验
  5. 船舶双向曲率板曲率可视化研究
  6. js数组对象的常用方法
  7. Java:ChronicleMap第1部分,精简版
  8. 最近幻影的两个ARP欺骗工具 挺不错的
  9. (136)System Verilog覆盖组参数传递实例
  10. eBPF学习记录(三)使用BCC开发eBPF程序
  11. 存储过程游标注意事项——表需要使用别名,如红色字体
  12. python网络编程01/网络协议
  13. java源码社团管理系统_基于jsp的社团管理系统-JavaEE实现社团管理系统 - java项目源码...
  14. android qq下载路径,手机qq下载的文件在哪个文件夹 查找路径解答
  15. 计算机交换机作用,交换机的作用与功能
  16. 爬取网易云音乐评论2
  17. 川农《劳动与社会保障法(本科)》21年12月作业考核
  18. 计算机主板设计与应用说明,电脑主板的主要参数及其含义介绍
  19. Python库中,如何使用jieba模块来实现古典名著《西游记》的分词
  20. PAT乙级1025题解

热门文章

  1. python 读grid 数据_如何将TextGrid文件的变量读入Python?
  2. python从入门到_python从入门到项目实践 (明日科技) 配套视频教程+源码
  3. C# image转byte[] byte[]转image
  4. shield tv android tv,NVIDIA老机顶盒SHIELD TV升级安卓7.0:国行眼巴巴
  5. python3读取多行数据合并_python3 数据规整化:清理、转换、合并、重塑(一)
  6. wlanconnect无法连接wifi_苹果iphone12无法连接wifi怎么回事 解决方法分享
  7. excel 制作dashboard_【实例分享】勤哲Excel服务器做影视制作企业管理系统
  8. 恐龙机器人钢索恐龙形态_恐龙有的四脚行走有的两脚行走,有的会飞有的会游,差别咋这么大...
  9. 在单链表中删除指定值的节点
  10. 【原】机器学习几个基本的问题