C++Builder Programming Forum
C++Builder  |  Delphi  |  FireMonkey  |  C/C++  |  Free Pascal  |  Firebird
볼랜드포럼 BorlandForum
 경고! 게시물 작성자의 사전 허락없는 메일주소 추출행위 절대 금지
C++빌더 포럼
Q & A
FAQ
팁&트릭
강좌/문서
자료실
컴포넌트/라이브러리
메신저 프로젝트
볼랜드포럼 홈
헤드라인 뉴스
IT 뉴스
공지사항
자유게시판
해피 브레이크
공동 프로젝트
구인/구직
회원 장터
건의사항
운영진 게시판
회원 메뉴
북마크
볼랜드포럼 광고 모집

C++빌더 Q&A
C++Builder Programming Q&A
[68288] Re:Pixel 연산 속도 개선에 대한 질문입니다.
Nibble [gameover] 2277 읽음    2012-10-01 14:19
TBitmap 의 PixelFormat 을 읽어보면 점 하나가 몇 개의 Byte 로 구성되는지 알 수 있습니다.
가령 pf24bit 라면 3바이트 고, pf32bit 라면 4바이트죠.
해당 y축 ScanLine을 (해당 y축 점들의 시작 주소를) 읽으셨으니 y 축 좌표가 해결된거고,
x축 좌표는 해당 ScanLine 시작주소에 byte 단위 * x 만큼 더해진 곳이면 됩니다.
byte 단위란게 결국 코드를 지저분하게 만드니 구조체/공용체(16bpp 이하에선 bit field 정의가 필요할 수도)를 쓰도록 합시다.

pf24bit 를 위해서는 다음과 같이 말이죠.

typedef struct TPixel24
{
     unsigned char r;
     unsigned char g;
     unsigned char b;
}TPixel24;

로테이션의 경우에는, 원시(source) 영상의 회전을 반영해 대상(destination) 영상에 사상(rasterize)하는게 목적이고,
이때 target 영상에는 ScanLine 방향의 순차쓰기, 원시 영상의 좌표는 계산을 통해얻어내는게
효율적입니다. 어차피 회전을 Point to Point 로 사상(nearest)하면 1픽셀 이내의 좌표오류가 생겨 왜곡이(자글거리게) 되니
결국 subsampling 이(bi-cubic 등등) 필요하게 됩니다.

for(int y = 0; y < pDstBitmap->Height; y++)
{
    TPixel24* dst = (TPixel24*)pDstBitmap->ScanLine[y];
    for(int x = 0; x < pDstBitmap->Width; x++)
    {
        int sx = -theta 만큼의 회전변환이 고려된 x 좌표 계산식;
        int sy = -theta 만큼의 회전변환이 고려된 y 좌표 계산식;
        TPixel24* src = (TPixel24*)pSrcBitmap->ScanLine[sy];
        dst[x] = src[sx];
    }
}

우리가 ScanLine 이라는 Property 를 이용하게 되면, 내부에 구현된 함수가 호출이 되게 됩니다.
y 좌표 갯수만큼 얻어지는 것은 크게 상관 없지만 O(n), (어떤방법을 쓰든 어차피 라인당 한 번은 얻어져야 하는것)
매 픽셀단위로 얻어지는것 O(n^2)은 바람직하지 못하죠.
그래서 src 도 y루프 시작 지점에서 한번만 얻어지게 수정되는게 맞습니다만,
회전변환의 특성상 각각의 픽셀이 다양한 y 좌표를 참조하게 되는 문제가 발생합니다.

해결책은 애초에 rotation 에 필요한 원시 영상을 배열구조에 옮겨 놓고 처리를 하시는게 경제적입니다.
TPixel24* srcBuffer = new TPixel24[w * h];
처럼 생긴 배열로요.

그랬다면 x루프 내부의 ScanLine 대신
(아래에서 w 는 pSrcBitmap->Width)

dst[x] = srcBuffer[sx + sy * w]; 와 같은 단순 연산이 사용될 수 있겠죠.

OpenGL, DirectX, OpenCV 나 델파이/빌더를 위해 간편히 구현된 Graphics32 와 같은
최적화된 라이브러리의 도움을 받으면 좋겠지만,
개념적인 설명도 필요할 것 같아 남겨 보았습니다.

성능이 관건일때 모든 픽셀에서 sx 와 sy 가 srcBuffer 의 영역을 벗어나는지 판단하는것은 비효율적입니다.
src 버퍼를 사각형 이미지의 장축 (대각선) 길이만큼 미리 가로 세로를 키워두는 것도 방법이고
(넓혀진 버퍼 한 중간에 원시 이미지를 복사),
각도에 따라 범위를 벗어날 가능성이 있는 부분들을 조각내(가로 3등분 * 세로 3등분 = 9등분) 별도로 처리하시는 것도 방법입니다.

권장하는 방법
1. source 영상의 대각선 방향의 반올림 크기 + 2~4픽셀(Sub sampling 및 반올림 오류 고려)을 장축이라 정의하자.
    int longLength = pSrcBitmap->Width + 4;
2. 장축 * 장축 크기의 srcBuffer 를 동적 할당.
    TPixel24* srcBuffer = new TPixel24[longLength * longLength];
3. source 영상을 srcBuffer의 한가운데에 복사할수 있도록 좌상단 시작점을 정의해 두자.
    int srcLeft = longLength - pSrcBitmap->Width >> 1;
    int srcTop = longLength - pSrcBitmap->Height >> 1;
4. source 영상을 srcBuffer의 한가운데에 복사 (나머지 영역은 0이나 회색, 마음에 드는 배경색으로 채워둔다)
5. 위에 언급한 코드 구조들을 이용해서 회전변환
    이때,
    TPixel24* src = srcBuffer + srcLeft + srcTop * longLength;
    를 y루프 시작부분에 이용하면 sx 와 sy 를 구하는 연산에 일일이 srcLeft 와 srcTop 을 더할 필요가 없고,
    음수와 원시영상의 폭을 초과한 sx, sy좌표에대한 if 문의 처리가 필요없다.
    이를 반영하기 위해 x에 대한 루프에서는
    tgt[x] = src[sx + sy * longLength];
    를 이용해야함.
    sin, cos 값들과 source 이미지의 점의 좌표를 고정소수점화(정수) 하면 조금 더 빨라질 수 있습니다.
    어느 한쪽이든 부동소수점을 사용한다면, 굳이 연산횟수를 늘리면서 한쪽만 고정소수화 할 필욘 없죠.
    어찌되었든 배열의 인자나 포인터 연산에 사용되기 위해서는 결국 한번은 정수로 바뀌어야 하니까요.
    (부동소수에서 정수로의 형변환, 부동소수와 정수간 연산은 오버헤드가 큽니다)
    이때 Sub-sampling 을 이용할 경우 rvalue 가 조합할 픽셀들의 가중치를 곱한후 합산한 값이 됨.
6. enjoy

게시판에 바로 작성한 글이라 오탈자가 있을 수 있습니다.
알파블렌딩 등 다양한 연산이 추가될꺼라면 24비트 보단 32비트 구성이 효율적입니다.
(4바이트 정렬의 어드레싱 가속의 잇점이 있음)

SEM80 님이 쓰신 글 :
: 안녕하세요.
: 오랜만에 볼랜드 포럼에 질문을 올립니다.
: 제가 요즘 영상처리 관련하여 프로그램을 구현하던 중 여태까지는 픽셀단위로 계산을 하다보니
: 연산 속도의 문제로 인하여 버퍼링 현상이 발생하는 문제가 발생하였습니다.
: 다음은 제가 현제 이미지 위에 글자를 로테이션 되게 하는 함수의 일부분입니다.
: 그런데 픽셀 연산 부분에서 버퍼링 현상이 발생합니다.
: 아래의 코드의에서 변경 전의 픽셀 연산 부분을 스캔라인으로 읽는 것까지는 별 문제 없이 진행하였으나
: 그 다음 코드를 어떻게 변환을 해서 픽셀의 위치를 어떻게 선언해야 되는지 잘 모르겠습니다.
:
: 보통 픽셀 위치 값을 가지고 로테이션을 했습니다. 스캔라인으로 로테이션을 할려니
: 어떻게 위치값을 정해야 될 지 모르겠습니다.
: 제가 아직 코드 부분을 올리는 방법을 몰라서^^;;
: 붙여넣기로 붙였습니다.
: 양해부탁드리며
: 좋은 답변 부탁드립니다.
:
: //////////////변경전//////////////////////////////////////////////
:    
:                TColor XYPixel;
:                TColor checkColor = clBlack;
:     if( textColor == clBlack )
:     {
:         checkColor = clWhite;
:     }
:     for( int i=iXStart; i<iXCount; i++ )
:     {
:         for( int j=iYStart; j<iYCount; j++ )
:         {
:             XYPixel = pSrcBitmap->Canvas->Pixels[i][j];  //<- 픽셀 연산 부분
:             if( XYPixel != checkColor )   //b
:             {
:                 iRX = i + iShiftX;
:                 iRY = j - iBoxHeight - iShiftY;
:                 iPX = (int)( (double)( (double)( iRX * dCoAlpha )
:                             - (double)( iRY * dSiAlpha ) ) + 0.5 ) + iX1;
:                 iPY = (int)( (double)( (double)( iRY * dCoAlpha )
:                             + (double)( iRX * dSiAlpha ) ) + 0.5 ) + iY1;
:
:                 if( iPX >= 0 && iPX < pDstBitmap->Width &&iPY >= 0 && iPY < pDstBitmap->Height )
:                 {
:                     pDstBitmap->Canvas->Pixels[iPX][iPY] = XYPixel;
:                 }
:             }
:         }
:     }
: ////////////////변경 후//////////////////////////
:     unsigned char         *rtColor;
:
:     for( int j=iYStart; j<iYCount; j++ )
:     {
:         rtColor = (unsigned char *)pSrcBitmap->ScanLine[j];
:     }
:
:     pDstBitmap->Canvas->Draw(0, 0, pSrcBitmap);

+ -

관련 글 리스트
68263 Pixel 연산 속도 개선에 대한 질문입니다. SEM80 1532 2012/09/27
68288     Re:Pixel 연산 속도 개선에 대한 질문입니다. Nibble 2277 2012/10/01
68404         감사합니다. SEM80 1329 2012/10/17
Google
Copyright © 1999-2015, borlandforum.com. All right reserved.