목적
우리는 다음과 같은 질문에 대한 답을 모색 할 것입니다 :
- 어떻게 이미지의 각 픽셀을 통과하는?
- OpenCV의 행렬 값은 어떻게 저장하는 것입니다?
- 어떻게 우리의 알고리즘의 성능을 구현 테스트?
- 어떤 테이블을 조회? 왜 그것을 사용?
테스트 케이스
여기에 우리가 테스트는 간단한 색상 감소 방법이다. 행렬의 요소는, 화소에 기억되어있는 하나의 채널 인 경우, C 또는 C ++ 부호 캐릭터 타입은 다음 픽셀 (256 개)은 서로 다른 값을 가질 수있다. 그러나 3 채널 영상 경우, 너무 많은의 저장 형식에 대한 색상의 수는 (또는 오히려 이상 천육백만가지있다). 우리는 너무 많은 색상이 사용 된 알고리즘의 성능에 심각한 영향을 미칠 수 있습니다. 사실, 때때로, 이러한 색상의 작은 부분은 동일한 효과를 얻기에 충분하다.
이 경우, 방법은 일반적으로 사용되는 컬러 공간을 감소 . 이 접근법은 : 기존의 색 공간은 적은 수의 컬러를 얻기 위해 입력 값으로 나누어진다. 예를 들어, 0 내지 9의 색상 값은, 바람직하게는 0.10 내지 19의 새로운 값이 바람직하게는 10 등이다.
UCHAR (부호 CHAR, 0과 255 사이의 수, 즉 숫자)를 입력 값으로 나눈 INT의 값은, 결과는 여전히 CHAR . 문자 유형은 결과로, 그래서 또한 소수점 아래로 반올림 추구. 이에 의해, 언급 UCHAR 정의 필드의 색상을 저감 동작 다음과 같은 형태로 표현 될 수있다 :
이 경우, 간단한 색 공간 감소 알고리즘은 다음의 두 단계로 표현 될 수있다 : a는 화소의 각 이미지 매트릭스를 통과 둘째, 상기 화학식 화소의 응용. 우리가 작업이 특히 많은 시간이 소요되었습니다 둘의 분열과 곱셈을 사용, 그래서 우리는 덧셈, 뺄셈, 할당 및 기타 작업과 같은 낮은 비용으로 대체해야한다는 주목할 만하다. 또한, 단지 값의 제한된 범위에 걸쳐 상기 입력 동작과 같은 것을 알아야한다 UCHAR 바람직하게는 256 값을 입력.
또, 볼 수있는 필요한 경우에 직접 할당 될 수있는 룩업 테이블을 사용하고,보다 큰 이미지를 들면, 미리 계산 가능한 모든 값에 유효 값을이. 인 계산없이 다른 입력 값만을 판독 장점을 룩업 테이블 또는 다차원 배열을, 상기 출력 값이 대응 저장되는 일차원.
우리의 테스트 프로그램 (물론 여기에 주어진 샘플 코드 등)는 다음과 같은 일을합니다 :
읽기 화상 (컬러 화상 커맨드 라인 파라미터에 의해 결정되는 그레이 스케일 이미지 일 수있다), 그 다음 주어진 커맨드 라인 파라미터 감색 명령 줄 인수의 정수이다.
현재 OpenCV의 화소에 의해 화상의 화소를 횡단 주로 세 가지 방법이있다. 우리는이 세 가지 방법은 이미지를 스캔 된 사용하고 화면에 시간을 출력합니다. 나는 이것이 매우 흥미로운 대비해야한다고 생각합니다.
당신은에서 얻을 수있는 여기 당신은 또한, CPP의 핵심 디렉토리의 tutorial_code를 입력 OpenCV의 샘플 디렉토리를 찾을 프로그램의 코드를 볼 수, 소스 코드를 다운로드합니다. 이 프로그램의 기본적인 사용법은 다음과 같습니다 :
how_to_scan_images imageName.jpg intValueToReduce [ G ]
마지막 인수는 선택 사항입니다. 제공 한 경우, 이미지는 그레이 스케일 형식으로 다른 색상 형식을로드. 이 프로그램에서, 우리는 먼저 조회 테이블을 계산합니다.
int divideWith; // convert our input string to number - C++ style
stringstream s;//数据类型转换
s << argv[2];
s >> divideWith;
if (!s)
{
cout << "Invalid number entered for dividing. " << endl;
return -1;
}
uchar table[256];
for (int i = 0; i < 256; ++i)
table[i] = divideWith* (i/divideWith);
这里我们先使用C++的 stringstream 类,把第三个命令行参数由字符串转换为整数。
然后,我们用数组和前面给出的公式计算查找表。这里并未涉及有关OpenCV的内容。
另外有个问题是如何计时。没错,OpenCV提供了两个简便的可用于计时的函数 getTickCount() 和 getTickFrequency() 。第一个函数返回你的CPU自某个事件(如启动电脑)以来走过的时钟周期数,第二个函数返回你的CPU一秒钟所走的时钟周期数。这样,我们就能轻松地以秒为单位对某运算计时:
double t = (double)getTickCount();
// 做点什么 ...
t = ((double)getTickCount() - t)/getTickFrequency();
cout << "Times passed in seconds: " << t << endl;
图像矩阵是如何存储在内存之中的?
图像矩阵的大小取决于我们所用的颜色模型,确切地说,取决于所用通道数。如果是灰度图像,矩阵就会像这样:
而对多通道图像来说,矩阵中的列会包含多个子列,其子列个数与通道数相等。例如,RGB颜色模型的矩阵:
注意到,子列的通道顺序是反过来的:BGR而不是RGB。很多情况下,因为内存足够大,可实现连续存储,因此,图像中的各行就能一行一行地连接起来,形成一个长行。连续存储有助于提升图像扫描速度,我们可以使用 isContinuous() 来去判断矩阵是否是连续存储的. 相关示例会在接下来的内容中提供。
1.高效的方法 Efficient Way
说到性能,经典的C风格运算符[](指针)访问要更胜一筹. 因此,我们推荐的效率最高的查找表赋值方法,还是下面的这种:
Mat& ScanImageAndReduceC(Mat& I, const uchar* const table)
{
// accept only char type matrices
CV_Assert(I.depth() != sizeof(uchar));
int channels = I.channels();
int nRows = I.rows * channels;
int nCols = I.cols;
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;
}
这里,我们获取了每一行开始处的指针,然后遍历至该行末尾。如果矩阵是以连续方式存储的,我们只需请求一次指针、然后一路遍历下去就行。彩色图像的情况有必要加以注意:因为三个通道的原因,我们需要遍历的元素数目也是3倍。
这里有另外一种方法来实现遍历功能,就是使用 data , data会从 Mat 中返回指向矩阵第一行第一列的指针。注意如果该指针为NULL则表明对象里面无输入,所以这是一种简单的检查图像是否被成功读入的方法。当矩阵是连续存储时,我们就可以通过遍历 data 来扫描整个图像。例如,一个灰度图像,其操作如下:
uchar* p = I.data;
for( unsigned int i =0; i < ncol*nrows; ++i)
*p++ = table[*p];
这回得出和前面相同的结果。但是这种方法编写的代码可读性方面差,并且进一步操作困难。同时,我发现在实际应用中,该方法的性能表现上并不明显优于前一种(因为现在大多数编译器都会对这类操作做出优化)。
2.迭代法 The iterator (safe) method
在高性能法(the efficient way)中,我们可以通过遍历正确的uchar域并跳过行与行之间可能的空缺-你必须自己来确认是否有空缺,来实现图像扫描,迭代法则被认为是一种以更安全的方式来实现这一功能。在迭代法中,你所需要做的仅仅是获得图像矩阵的begin和end,然后增加迭代直至从begin到end。将*操作符添加在迭代指针前,即可访问当前指向的内容。
Mat& ScanImageAndReduceIterator(Mat& I, const uchar* const table)
{
// accept only char type matrices
CV_Assert(I.depth() != sizeof(uchar));
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;
}
对于彩色图像中的一行,每列中有3个uchar元素,这可以被认为是一个小的包含uchar元素的vector,在OpenCV中用 Vec3b 来命名。如果要访问第n个子列,我们只需要简单的利用[]来操作就可以。需要指出的是,OpenCV的迭代在扫描过一行中所有列后会自动跳至下一行,所以说如果在彩色图像中如果只使用一个简单的 uchar 而不是 Vec3b 迭代的话就只能获得蓝色通道(B)里的值。
3. 通过相关返回值的On-the-fly地址计算
事实上这个方法并不推荐被用来进行图像扫描,它本来是被用于获取或更改图像中的随机元素。它的基本用途是要确定你试图访问的元素的所在行数与列数。在前面的扫描方法中,我们观察到知道所查询的图像数据类型是很重要的。这里同样的你得手动指定好你要查找的数据类型。下面的代码中是一个关于灰度图像的示例(运用 + at() 函数):
Mat& ScanImageAndReduceRandomAccess(Mat& I, const uchar* const table)
{
// accept only char type matrices
CV_Assert(I.depth() != sizeof(uchar));
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;
}
该函数输入为数据类型及需求元素的坐标,返回的是一个对应的值-如果用 get 则是constant,如果是用 set 、则为non-constant. 出于程序安全,当且仅当在 debug 模式下 它会检查你的输入坐标是否有效或者超出范围. 如果坐标有误,则会输出一个标准的错误信息. 和高性能法(the efficient way)相比, 在 release模式下,它们之间的区别仅仅是On-the-fly方法对于图像矩阵的每个元素,都会获取一个新的行指针,通过该指针和[]操作来获取列元素。
当你对一张图片进行多次查询操作时,为避免反复输入数据类型和at带来的麻烦和浪费的时间,OpenCV 提供了:basicstructures:Mat_ <id3> data type. 它同样可以被用于获知矩阵的数据类型,你可以简单利用()操作返回值来快速获取查询结果. 值得注意的是你可以利用 at() 函数来用同样速度完成相同操作. 它仅仅是为了让懒惰的程序员少写点 >_< .
4. 核心函数LUT(The Core Function)
这是最被推荐的用于实现批量图像元素查找和更改操作的图像方法。在图像处理中,对于一个给定的值,将其替换成其他的值是一个很常见的操作,OpenCV提供里一个函数直接实现该操作,并不需要你自己扫描图像,就是:operationsOnArrays:LUT() <lut> ,一个包含于core module的函数. 首先我们建立一个mat型用于查表:
Mat lookUpTable(1, 256, CV_8U);
uchar* p = lookUpTable.data;
for( int i = 0; i < 256; ++i)
p[i] = table[i];
然后我们调用函数 (I 是输入 J 是输出):
LUT(I, lookUpTable, J);
性能表现
为了得到最优的结果,你最好自己编译并运行这些程序. 为了更好的表现性能差异,我用了一个相当大的图片(2560 X 1600). 性能测试这里用的是彩色图片,结果是数百次测试的平均值.
Efficient Way | 79.4717 milliseconds |
Iterator | 83.7201 milliseconds |
온 - 더 - 플라이 RA | 93.7878 밀리 초 |
LUT 함수 | 32.5759 밀리 초 |
우리는 어떤 결론을 내릴 : OpenCV의 라이브러리는 물론 인텔 스레드에서 멀티 스레드 아키텍처를 가능하게 할 수 있기 때문에 당신이, 스캔 한 이미지를 반복 포인터를 사용하는 방법을 선호하는 경우, 가장 빠른을 얻을 수 있습니다 OpenCV의 LUT 기능이라는 내장 함수의 사용을 만들기 위해 ... 프랑스는 좋은 선택하지만, 느린 속도입니다. 방법은 릴리스 모드에서 전체지도는 자원의 방법 대부분의 낭비 검색에 - 더 - 플라이 디버그 모드에서 사용, 거의 동일한 성능 및 반복적 인 방법이지만, 보안 관점에서, 반복적 인 방법은 더 나은 선택이다
HTTPS : //www.cnblogs.com/james1207/p/3266645.html 재현