初次接触OpenCV的开发者,必须过的第一道坎就是学会如何遍历访问Mat对象中每个像素,实现像素级别的图像操作,这个是最级别的编程技能,但是不同的像素遍历方法效率有云泥之别,相差特别大,甚至可能成为算法运行的瓶颈之一,因此找到一种速度快的遍历方法对大图像处理是很关键的。在开始寻找高效遍历方法之前,先来了解一下Mat对象的数据组织形式与像素块数据的存储方式,Mat对象由两个部分组成,元数据头部加像素数据块部分,图示如下:
在OpenCV C++中Mat对象的内存管理由OpenCV框架自动负责内存分配与回收,基于智能指针实现内存管理。
三种遍历方法
方法一
基于Mat对象的随机像素访问API实现,通过行列索引方式遍历每个像素值。代码实现如下
void method_1(Mat &image) {
double t1 = getTickCount();
int w = image.cols;
int h = image.rows;
for (int row = 0; row < h; row++) {
for (int col = 0; col < w; col++) {
Vec3b bgr = image.at<Vec3b>(row, col);
bgr[0] = 255 - bgr[0];
bgr[1] = 255 - bgr[1];
bgr[2] = 255 - bgr[2];
image.at<Vec3b>(row, col) = bgr;
}
}
double t2 = getTickCount();
double t = ((t2 - t1) / getTickFrequency()) * 1000;
ostringstream ss;
ss << "Execute time : " << std::fixed << std::setprecision(2) << t << " ms ";
putText(image, ss.str(), Point(20, 20), FONT_HERSHEY_SIMPLEX, 0.75, Scalar(0, 0, 255), 2, 8);
imshow("result", image);
}
方法二
基于Mat对象的行随机访问指针方式实现对每个像素的遍历,代码实现如下:
void method_2(Mat &image) {
double t1 = getTickCount();
int w = image.cols;
int h = image.rows;
for (int row = 0; row < h; row++) {
Vec3b* curr = image.ptr<Vec3b>(row);
for (int col = 0; col < w; col++) {
Vec3b bgr = curr[col];
bgr[0] = 255 - bgr[0];
bgr[1] = 255 - bgr[1];
bgr[2] = 255 - bgr[2];
}
}
double t2 = getTickCount();
double t = ((t2 - t1) / getTickFrequency()) * 1000;
ostringstream ss;
ss << "Execute time : " << std::fixed << std::setprecision(2) << t << " ms ";
putText(image, ss.str(), Point(20, 20), FONT_HERSHEY_SIMPLEX, 0.75, Scalar(0, 0, 255), 2, 8);
imshow("result", image);
}
除了上述的行指针遍历方式,常见的行指针还有如下:
CV_8UC1: 灰度图像
uchar* ptr = image.ptr<uchar>(row_index);
CV_8UC3: 彩色图像
Vec3b* ptr = image.ptr<cv::Vec3b>(row_index);
CV_32FC1: 单通道浮点数图像
float* ptr = image.ptr<float>(row_index);
CV_32FC3: 三通道浮点数图像
Vec3f* ptr = image.ptr<cv::Vec3f>(row_index);
方法三
直接获取Mat对象的像素块的数据指针,基于指针操作,实现快速像素方法,代码实现如下:
void method_3(Mat &image) {
double t1 = getTickCount();
int w = image.cols;
int h = image.rows;
for (int row = 0; row < h; row++) {
uchar* uc_pixel = image.data + row*image.step;
for (int col = 0; col < w; col++) {
uc_pixel[0] = 255 - uc_pixel[0];
uc_pixel[1] = 255 - uc_pixel[1];
uc_pixel[2] = 255 - uc_pixel[2];
uc_pixel += 3;
}
}
double t2 = getTickCount();
double t = ((t2 - t1) / getTickFrequency()) * 1000;
ostringstream ss;
ss << "Execute time : " << std::fixed << std::setprecision(2) << t << " ms ";
putText(image, ss.str(), Point(20, 20), FONT_HERSHEY_SIMPLEX, 0.75, Scalar(0, 0, 255), 2, 8);
imshow("result", image);
}
实验对比结果
系统信息与软件版本
输入图像大小为:1280x720, 彩色
操作系统:win10
OpenCV版本:OpenCV4.1
CPU:core i7 8th
实事证明,唯一正确的选择是直接使用data指针直接访问,但是这个在OpenCV官方的教程都没有明确说明,官方教程代码都是基于第一种方式,我想主要是让初学者容易理解与入门,这个也导致一些人在做开发的时候直接使用第一种方式做遍历,然后就是代码运行太慢,以后请用正确方式打开Mat对象遍历....
我自己也写代码测试了一下;flip比method_3有时慢一点,method_3确实3ms左右。
using namespace std;
using namespace cv;
void colorReduce(const Mat& image, int div)
{
Mat outImage;
double t1 = getTickCount();
outImage.create(image.size(), image.type());
MatConstIterator_<Vec3b> it_in = image.begin<Vec3b>();
MatConstIterator_<Vec3b> itend_in = image.end<Vec3b>();
MatIterator_<Vec3b> it_out = outImage.begin<Vec3b>();
MatIterator_<Vec3b> itend_out = outImage.end<Vec3b>();
while (it_in != itend_in)
{
(*it_out)[0] = (*it_in)[0];// / div * div + div / 2;
(*it_out)[1] = (*it_in)[1];// / div * div + div / 2;
(*it_out)[2] = (*it_in)[2];// / div * div + div / 2;
it_in++;
it_out++;
}
double t2 = getTickCount();
double t = ((t2 - t1) / getTickFrequency()) * 1000;
ostringstream ss;
ss << "colorReduce time : " << std::fixed << std::setprecision(2) << t << " ms ";
putText(outImage, ss.str(), Point(20, 20), FONT_HERSHEY_SIMPLEX, 0.75, Scalar(0, 0, 255), 2, 8);
imshow("colorReduce", outImage);
waitKey(1);
}
void Flip(Mat &img)
{
double t1 = getTickCount();
int rows = img.rows;
int cols = img.cols;
for (int i = 0; i < rows / 2; i++)
{
uchar *p = img.ptr<uchar>(i);
uchar *q = img.ptr<uchar>(rows - 1 - i);
uchar t;
for (int j = 0; j < cols; j++)
{
t = *p; *p++ = *q; *q++ = t;
t = *p; *p++ = *q; *q++ = t;
t = *p; *p++ = *q; *q++ = t;
}
}
double t2 = getTickCount();
double t = ((t2 - t1) / getTickFrequency()) * 1000;
ostringstream ss;
ss << "Flip time : " << std::fixed << std::setprecision(2) << t << " ms ";
putText(img, ss.str(), Point(20, 20), FONT_HERSHEY_SIMPLEX, 0.75, Scalar(0, 0, 255), 2, 8);
imshow("Flip", img);
waitKey(1);
}
void method_3(Mat &image) {
double t1 = getTickCount();
int w = image.cols;
int h = image.rows;
for (int row = 0; row < h; row++) {
uchar* uc_pixel = image.data + row * image.step;
for (int col = 0; col < w; col++) {
uc_pixel[0] = 255 - uc_pixel[0];
uc_pixel[1] = 255 - uc_pixel[1];
uc_pixel[2] = 255 - uc_pixel[2];
uc_pixel += 3;
}
}
double t2 = getTickCount();
double t = ((t2 - t1) / getTickFrequency()) * 1000;
ostringstream ss;
ss << "method_3 time : " << std::fixed << std::setprecision(2) << t << " ms ";
putText(image, ss.str(), Point(20, 20), FONT_HERSHEY_SIMPLEX, 0.75, Scalar(0, 0, 255), 2, 8);
imshow("result", image);
waitKey(0);
}
int main(int argc, const char * argv[]) {
const int a1 = 10;
auto b1 = a1; //b1的类型为int而非const int(去除const)
const auto c1 = a1;//此时c1的类型为const int
b1 = 100;//合法
//c1 = 100;//非法
char filename[100];
double threshold = 7.7;
int num = 4;
// int *pia = new int[num] (); // 每个元素初始化为0
for (int k = 1; k < num; k++) {
//sprintf(filename, "D:/data/pic/%d.png", 1);
sprintf(filename, "D:\\data\\test\\0720_mouse/%d.jpg", k);
//Mat x1 = imread(filename, IMREAD_GRAYSCALE);
Mat x1 = imread(filename, 1);
imshow("x1", x1);
waitKey(1);
Mat img_copy;
x1.copyTo(img_copy);
Flip(img_copy);
colorReduce(x1, 2);
method_3(x1);