讲如何比较各个遍历方式的效率。
效率是图像处理关心的一个问题。这就要检查处理效率,进而找到程序的性能瓶颈。
但是,程序的性能与条理之间,需要做好权衡。
1.执行时间衡量
cv::getTickCount() 用于获取自计算机启动后,经过的时钟周期数。
cv::getTickFrequency() 用于获取每秒时钟周期数。
将 “结束时周期” 减去 “开始时的周期” 得到 被测量代码 “消耗的周期数”。最后,除以每秒周期数,来作为代码的执行耗时。
cpp 11 以上可以借助 chrono 函数来处理计时。
#include <chrono> auto start = std::chrono::high_resolution_clock::now(); //process auto end = std::chrono::high_resolution_clock::now(); std::chrono::duration<double> duration = end - start; std::cout << "colorReduce_pointer used time: " << duration.count() << "s" << std::endl;
实现方式如下:
const int64 start = cv::getTickCount();
//some process
//elapsed time in seconds
const int64 duration = (cv::getTickCount() - start) / cv::getTickFrequency();
具体的绝对执行时间与机器有关。并且也与编译程序的编译器、编译条件有关。
包含少量语句的较短循环通常比包含单个语句的较长循环执行效率更高,即使处理的元素总数相同。类似地,如果你有不同的计算应用到一个像素,在一个循环中应用它们,而不是写一个连续的循环,每个计算一个。
多线程是提高算法效率的另一种方法。但是这里先不做深入。
2.完整代码
#include <iostream>
#include <opencv2/opencv.hpp>
#include <chrono>
//指针实现
void colorReduce_pointer(cv::Mat/*&*/ image, int div)
{
int row, col;
// 每行图像的数据元素个数
for (size_t i = 0; i < image.rows; i++)
{
// 得到第 i 行的数据指针
uchar* data = image.ptr<uchar>(i);
for(size_t j = 0; j < (image.cols * image.channels()); j++)
{
// 处理第 i 行第 j 列 像素数据
// 不用区分灰度图和彩色图片了,每次移动一个通道数据字段
data[j] = data[j] / div * div + div / 2;
}
}
}
//指针实现
void colorReduce_pointer_v2(cv::Mat/*&*/ image, int div)
{
int row, col;
// 每行图像的数据元素个数
size_t colsdataCnt = image.cols * image.channels();
// 预先计算出结果,避免不必要的重复计算
uint64 div2 = div / 2;
for (size_t i = 0; i < image.rows; i++)
{
// 得到第 i 行的数据指针
uchar* data = image.ptr<uchar>(i);
for(size_t j = 0; j < colsdataCnt; j++)
{
// 处理第 i 行第 j 列 像素数据
// 不用区分灰度图和彩色图片了,每次移动一个通道数据字段
data[j] = data[j] / div * div + div2;
}
}
}
//at 方法, 非常慢,只能说在别无选择时,做随机像素访问用。不适用与遍历扫描像素
void colorReduce_at(cv::Mat/*&*/ image, int div)
{
// 预先计算出结果,避免不必要的重复计算
uint64 div2 = div / 2;
for (size_t i = 0; i < image.rows; i++)
{
// 得到第 i 行的数据指针
uchar* data = image.ptr<uchar>(i);
// 处理第 i 行第 j 列 像素数据
for(size_t j = 0; j < image.cols; j++)
{
image.at<cv::Vec3b>(i,j)[0] = image.at<cv::Vec3b>(i,j)[0] / div * div + div2;
image.at<cv::Vec3b>(i,j)[1] = image.at<cv::Vec3b>(i,j)[1] / div * div + div2;
image.at<cv::Vec3b>(i,j)[2] = image.at<cv::Vec3b>(i,j)[2] / div * div + div2;
}
}
}
//迭代器实现
void colorReduce_iterator(cv::Mat/*&*/ image, int div)
{
int row, col;
//只是对彩色图像的处理
cv::Mat_<cv::Vec3b>::iterator it = image.begin<cv::Vec3b>();
cv::Mat_<cv::Vec3b>::iterator it_end = image.end<cv::Vec3b>();
for ( ; it != it_end; it++)
{
//对每个通道进行计算
(*it)[0] = ((*it)[0] / div) * div + div / 2;
(*it)[1] = ((*it)[1] / div) * div + div / 2;
(*it)[2] = ((*it)[2] / div) * div + div / 2;
}
}
//reshape 方式
void colorReduce_reshape(cv::Mat image, int div)
{
//数据连续
if (image.isContinuous())
{
//reshape 方法改变矩阵维度,而不需要任何内存复制或重新分配。列数,会自动做对应调整。
//实现对矩阵元素的序列化
image.reshape(1, // 通道数
1);// 行数
//std::cout << "image is Continuous." << std::endl;
}
int row = image.rows;
size_t colsdataCnt = image.cols * image.channels();// 此时这里的 cols,就相当于原来的 行数 * 列数
// 每行图像的数据元素个数
for (size_t i = 0; i < row; i++)
{
// 得到第 i 行的数据指针
uchar* data = image.ptr<uchar>(i);
for(size_t j = 0; j < colsdataCnt; j++)
{
// 处理第 i 行第 j 列 像素数据
// 不用区分灰度图和彩色图片了,每次移动一个通道数据字段
data[j] = data[j] / div * div + div / 2;
}
}
}
//保持不修改原图,另外返回结果图片
//可以同时传入同一张图,然后会同源修改
//或者传入一张新的图片,另外修改
void colorReduce(const cv::Mat& origin,cv::Mat& result, int div)
{
int row, col;
//利用 cv::Mat 的creat 方法封装的机制,
//如果提供的参数与当前Mat具有相同的行、列数,以及数据类型。将什么也不做,立即返回。
//如果不同,则创建一个新内存区域
//这将使得,调用时使用同一张图片,则不会重复创建内存
result.create(origin.size(), origin.type());
// 每行图像的数据元素个数
size_t colsdataCnt = result.cols * result.channels();
for (size_t i = 0; i < result.rows; i++)
{
// 分别得到第 i 行的数据指针
const uchar* data_origin = origin.ptr<uchar>(i);
uchar* data_result = result.ptr<uchar>(i);
for(size_t j = 0; j < colsdataCnt; j++)
{
// 处理第 i 行第 j 列 像素数据
// 不用区分灰度图和彩色图片了,每次移动一个通道数据字段
data_result[j] = data_origin[j] / div * div + div / 2;
}
}
}
int main(int argc, char *argv[])
{
// 检查命令行参数
if (argc != 4)
{
std::cerr << "Usage: " << argv[0] << " <input_image> <colorReduce> <output_image>" << std::endl;
return -1;
}
// 读取输入图像和logo图像
cv::Mat input_image = cv::imread(argv[1]);
// 检查输入图像和logo图像是否成功读取
if (input_image.empty())
{
std::cerr << "Error: Could not open or find input image" << std::endl;
}
cv::namedWindow("input_image", cv::WINDOW_NORMAL);
cv::imshow("input_image", input_image);
cv::waitKey(0);
int reduce = std::stof(argv[2]);
if (reduce < 0 || reduce > 255)
{
std::cerr << "Error: colorReduce must be between 0 and 255" << std::endl;
return -1;
}
{
const int64 start = cv::getTickCount();
colorReduce_pointer(input_image, reduce);
const int64 end = cv::getTickCount();
std::cout << "colorReduce_pointer used time: " << (end - start) / cv::getTickFrequency() << "s" << std::endl;
}
{ //演示 chrono
auto start = std::chrono::high_resolution_clock::now();
colorReduce_pointer(input_image, reduce);
auto end = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> duration = end - start;
std::cout << "colorReduce_pointer (chrono) used time: " << duration.count() << "s" << std::endl;
}
{
const int64 start = cv::getTickCount();
colorReduce_pointer_v2(input_image, reduce);
const int64 end = cv::getTickCount();
std::cout << "colorReduce_pointer_v2 used time: " << (end - start) / cv::getTickFrequency() << "s" << std::endl;
}
{
const int64 start = cv::getTickCount();
colorReduce_at(input_image, reduce);
const int64 end = cv::getTickCount();
std::cout << "colorReduce_at used time: " << (end - start) / cv::getTickFrequency() << "s" << std::endl;
}
{
const int64 start = cv::getTickCount();
colorReduce_iterator(input_image, reduce);
const int64 end = cv::getTickCount();
std::cout << "colorReduce_iterator used time: " << (end - start) / cv::getTickFrequency() << "s" << std::endl;
}
{
const int64 start = cv::getTickCount();
colorReduce_reshape(input_image, reduce);
const int64 end = cv::getTickCount();
std::cout << "colorReduce_reshape used time: " << (end - start) / cv::getTickFrequency() << "s" << std::endl;
}
{
cv::Mat newImage = input_image.clone();
//inplace 修改原图方式
const int64 start = cv::getTickCount();
colorReduce(newImage,newImage, reduce);
const int64 end = cv::getTickCount();
std::cout << "colorReduce (in-place process) used time: " << (end - start) / cv::getTickFrequency() << "s" << std::endl;
}
{
cv::Mat newImage;
//colorReduce 会reallocate 内存 会增加耗时
const int64 start = cv::getTickCount();
colorReduce(input_image,newImage, reduce);
const int64 end = cv::getTickCount();
std::cout << "colorReduce (reallocate memory) used time: " << (end - start) / cv::getTickFrequency() << "s" << std::endl;
}
cv::imwrite(argv[3], input_image);
cv::namedWindow("output_image", cv::WINDOW_NORMAL);
cv::imshow("output_image", input_image);
cv::waitKey(0);
return 0;
}
对同一图像的处理时间的比较,colorReduce_iterator 慢的很稳定,指针方式要比它快很多。
两个指针方法中,多行和一行遍历的时间差不多。colorReduce_pointer_v2 版本也没有快许多,可能是编译器层面做了优化。
通过避免循环中不必要的重复计算也可以提高效率。
重新分配内存的处理方式,这里没有看的太明显,但是要知道,重新分配内存是耗时的操作。
$ ./build/document_processor build/2.jpg 8 build/2_out.jpg
colorReduce_pointer used time: 0.113196s
colorReduce_pointer (chrono) used time: 0.103103s
colorReduce_pointer_v2 used time: 0.0836637s
colorReduce_at used time: 0.501025s
colorReduce_iterator used time: 0.315552s
colorReduce_reshape used time: 0.0980302s
colorReduce (in-place process) used time: 0.100326s
colorReduce (reallocate memory) used time: 0.0879106s
$ ./build/document_processor build/2.jpg 64 build/2_out.jpg
colorReduce_pointer used time: 0.0888434s
colorReduce_pointer (chrono) used time: 0.0986318s
colorReduce_pointer_v2 used time: 0.0872247s
colorReduce_at used time: 0.402873s
colorReduce_iterator used time: 0.264107s
colorReduce_reshape used time: 0.0916848s
colorReduce (in-place process) used time: 0.0893917s
colorReduce (reallocate memory) used time: 0.0915852s
1167

被折叠的 条评论
为什么被折叠?



