|
Nibble님 감사합니다.^^
Nibble 님이 쓰신 글 :
: 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);
|