opencv自带例子学习-如何扫描浏览图像

opencv自带例子学习-如何扫描浏览图像

##数字图像

经数字化的图像,一般用一个矩阵来表示。如一张800x600的图像,在程序中是由一个800x600大小的矩阵表示,一张图像就是由这800x600个点组成,在彩色图像和灰度图像上面有所区别,彩色图像是三通道的,即每一个点有RGB三个元素,灰度图像则是一个点只有一个元素。另外特殊的是还有4通道的图像,除了RGB三原色外,4通道图像还有一个alpha通道,用来表示图像的透明度。

数字图像矩阵表示

##索引图像

索引图像也称为调色版图像,索引图像提供了一个方法减少表达一幅图像所需的数据量。原理是颜色值构成一个查找表(look up table),也称调色板,如8位灰度级的彩色图像理论上拥有256x256x256总颜色,但是通过查找表的方式,一个像素点可以以记录(126,24,44)的方式去查找调色版中的图像,无需每个像素点直接记录256x256x256这么多中颜色里面的其中一种颜色。数字图像里图像一般以这种形式储存图像颜色信息,那如何去扫描访问图像的像素信息,opencv中的示例how_to_scan_image.cpp给出了答案

示例程序

下面是示例源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
//头文件
#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);//通过c指针的方式扫描
Mat& ScanImageAndReduceIterator(Mat& I, const uchar* table);//通过迭代器的方式扫描
Mat& ScanImageAndReduceRandomAccess(Mat& I, const uchar * table);//通过at函数访问

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);//灰度图
else
I = 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++ style
stringstream 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();//记录晶振起点

//c语言指针风格访问测试
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]
//创建初始化查找表lookuptable
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);//使用LUT函数将I数据处理后存入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 matrices
CV_Assert(I.depth() == CV_8U);

int channels = I.channels();//通道数

int nRows = I.rows;//行数
int nCols = I.cols * channels;//像素点的列数 = 列数x通道数

if (I.isContinuous())
{
nCols *= nRows;
nRows = 1;
}

int i,j;
uchar* p;//c语义指针形式
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 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;
}
//! [scan-iterator]

//! [scan-random]
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)];//使用at函数
break;
}
case 3:
{
Mat_<Vec3b> _I = I;//使用<cv::Vec3b>

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]

程序知识点

三种访问方式

c语言指针方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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;//像素点的列数 = 列数x通道数

if (I.isContinuous())
{
nCols *= nRows;
nRows = 1;
}

int i,j;
uchar* p;//c语义指针形式
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类型的指针记录行地址,然后遍历一行中所有的像素点。下一行继续如此。

迭代器方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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;
}

迭代器的访问方式也简单,首先是记录迭代的开始begin,一直迭代到结束end,不包含end。关键语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
MatIterator_<uchar> it, end;
for( it = I.begin<uchar>(), end = I.end<uchar>(); it != end; ++it)//迭代器访问
*it = table[*it];
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]];
}
//使用c++新标准上述代码还可以简化为
for( auto it = I.begin<uchar>(), end = I.end<uhar>(); it != end; ++it)
*it = table[*it];
for(auto 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]];
}

at函数方式

1
2
3
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)];//使用at函数

at的方式是比较常用的方法了,我一般也是使用这个方法,这个方法访问有一个优点,就是相比指针的访问形式,这个方法可检查数组是否越界,如果数组越界就会报out_of_range错误,这样对调试就可以避免一些不必要的错误。这个方式缺点就是访问速度是稍慢的,在debug调试模式之下尤为明显,但是release之下速度与其他方式倒是差距不大。

统计程序运行耗费时间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
t = (double)getTickCount();//记录晶振起点

//c语言指针风格访问测试
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;

原理大概是这样的,计算机里有个叫晶振的东西,一直在按照固定的频率去”震动”,这个震动形成的就是为计算机计算提供动力的电信号,一般是方波,也称时钟信号。这个震动频率可以理解为计算机处理器的主频,即计算速度。所以计算耗时的原理可以解释为

1
2
3
4
5
6
7
8
//1.记录震动的起始点
t = (double)getTickCount();
//2.程序运行
...
//3.记录震动的结束点,减去起始点,就得到程序运行过程中震动的次数
(double)getTickCount() - t
//4.结果处以震动的频率(也就是震动的速度),就得到整个过程的时间,时间单位时s
1000*((double)getTickCount() - t)/getTickFrequency();//*1000后即ms

LUT函数

1
2
3
4
5
6
7
8

//! [table-init]
//创建初始化查找表lookuptable
Mat lookUpTable(1, 256, CV_8U);
uchar* p = lookUpTable.ptr();
for( int i = 0; i < 256; ++i)
p[i] = table[i];
//! [table-init]

函数原型

1
2
3
4
5
6
void cv::LUT	(	InputArray 	src,
InputArray lut,
OutputArray dst
)
Python:
dst = cv.LUT( src, lut[, dst] )

函数执行的操作

LUT

由图可知,LUT函数执行的操作也很简单,即对输入图像每个像素点加上一个d,然后输出。这个d有条件限制的,输入图像是8位无符号时d为0,8位有符号时为128。

回顾总结

数字图像由一个矩阵表示,不同图像一个像素点有不一样的通道数,灰度图一个通道,彩色图3个通道,有些图像多一个alpha的透明的通道。本例学习了c语言指针方式、迭代器和at函数方式访问像素,at访问方式是安全的。计算程序耗时和LUT函数。

-------------本文结束感谢您的阅读-------------