OpenCV의 연구 노트 : 조회 테이블과 타이밍을 사용하여 이미지를 스캔하는 방법

원본 링크 : http://www.cnblogs.com/james1207/p/3266645.html

 목적

우리는 다음과 같은 질문에 대한 답을 모색 할 것입니다 :

  • 어떻게 이미지의 각 픽셀을 통과하는?
  • OpenCV의 행렬 값은 어떻게 저장하는 것입니다?
  • 어떻게 우리의 알고리즘의 성능을 구현 테스트?
  • 어떤 테이블을 조회? 왜 그것을 사용?

 테스트 케이스

여기에 우리가 테스트는 간단한 색상 감소 방법이다. 행렬의 요소는, 화소에 기억되어있는 하나의 채널 인 경우, C 또는 C ++ 부호 캐릭터 타입은 다음 픽셀 (256 개)은 서로 다른 값을 가질 수있다. 그러나 3 채널 영상 경우, 너무 많은의 저장 형식에 대한 색상의 수는 (또는 오히려 이상 천육백만가지있다). 우리는 너무 많은 색상이 사용 된 알고리즘의 성능에 심각한 영향을 미칠 수 있습니다. 사실, 때때로, 이러한 색상의 작은 부분은 동일한 효과를 얻기에 충분하다.

이 경우, 방법은 일반적으로 사용되는  컬러 공간을 감소  . 이 접근법은 : 기존의 색 공간은 적은 수의 컬러를 얻기 위해 입력 값으로 나누어진다. 예를 들어, 0 내지 9의 색상 값은, 바람직하게는 0.10 내지 19의 새로운 값이 바람직하게는 10 등이다.

UCHAR  (부호 CHAR, 0과 255 사이의 수, 즉 숫자)를 입력 값으로 나눈  INT의  값은, 결과는 여전히  CHAR  . 문자 유형은 결과로, 그래서 또한 소수점 아래로 반올림 추구. 이에 의해, 언급  UCHAR  정의 필드의 색상을 저감 동작 다음과 같은 형태로 표현 될 수있다 :

{신규} I_ = (\ FRAC {} I_ {오래된} {10}) * 10

이 경우, 간단한 색 공간 감소 알고리즘은 다음의 두 단계로 표현 될 수있다 : 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;

 图像矩阵是如何存储在内存之中的?

图像矩阵的大小取决于我们所用的颜色模型,确切地说,取决于所用通道数。如果是灰度图像,矩阵就会像这样:

\newcommand{\tabItG}[1] { \textcolor{black}{#1} \cellcolor[gray]{0.8}}\begin{tabular} {ccccc}~ & \multicolumn{1}{c}{Column 0} &   \multicolumn{1}{c}{Column 1} &   \multicolumn{1}{c}{Column ...} & \multicolumn{1}{c}{Column m}\\Row 0 & \tabItG{0,0} & \tabItG{0,1} & \tabItG{...}  & \tabItG{0, m} \\Row 1 & \tabItG{1,0} & \tabItG{1,1} & \tabItG{...}  & \tabItG{1, m} \\Row ... & \tabItG{...,0} & \tabItG{...,1} & \tabItG{...} & \tabItG{..., m} \\Row n & \tabItG{n,0} & \tabItG{n,1} & \tabItG{n,...} & \tabItG{n, m} \\\end{tabular}

而对多通道图像来说,矩阵中的列会包含多个子列,其子列个数与通道数相等。例如,RGB颜色模型的矩阵:

\newcommand{\tabIt}[1] { \textcolor{yellow}{#1} \cellcolor{blue} &  \textcolor{black}{#1} \cellcolor{green} & \textcolor{black}{#1} \cellcolor{red}}\begin{tabular} {ccccccccccccc}~ & \multicolumn{3}{c}{Column 0} &   \multicolumn{3}{c}{Column 1} &   \multicolumn{3}{c}{Column ...} & \multicolumn{3}{c}{Column m}\\Row 0 & \tabIt{0,0} & \tabIt{0,1} & \tabIt{...}  & \tabIt{0, m} \\Row 1 & \tabIt{1,0} & \tabIt{1,1} & \tabIt{...}  & \tabIt{1, m} \\Row ... & \tabIt{...,0} & \tabIt{...,1} & \tabIt{...} & \tabIt{..., m} \\Row n & \tabIt{n,0} & \tabIt{n,1} & \tabIt{n,...} & \tabIt{n, m} \\\end{tabular}

注意到,子列的通道顺序是反过来的: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 재현

추천

출처blog.csdn.net/weixin_30193897/article/details/94986181