ex)
for (int i = 0; i < img->height;i++)
{
for (int j = 0; j < img->width;j++)
{
// 데이터 직접 접근 /제어
int index = j + i * img->widthStep ;
unsigned char n_indx = gray->imageData[index] ; // 해당 픽셀에 접근하여 n_indx 에 픽셀값 저장
if(n_indx < 100) //픽셀 값이 100보다 작을경우
{
img->imageData[j+i*(img->widthStep)] = 0; // img의 픽셀 값을 제어
}
}
}
IplImage의 char*로 픽셀값을 가지고 있는 imageData 멤버에직접 접근하는 방법이 있습니다. (예제참조)
아래에
채널 1개의 그래디언트이미지를 생성하는 예제입니다.
예제코드> 주석 처리 되어 있는 방법1, 방법2, 방법3 어떤 것을 사용해도 모두 같은 결과를 보여 줍니다.
void jvPrintoutPixelValueByArray() {
int nWidth=256; int nHeight=256;
IplImage* firstImage = cvCreateImage( cvSize(nWidth,nHeight), IPL_DEPTH_8U, 1);
int x,y;
for(y=0;y<nHeight;++y)
for(x=0;x<nWidth;++x)
{
// cvSet2D( firstImage, y, x, cvScalar(y)); // 방법 1
// cvSetReal2D( firstImage, y, x, (double)y); // 방법 2
firstImage->imageData[x+y*(firstImage->widthStep)] = x; //방법 3 x 값을 대입한다
}
// 화면에 영상출력
cvNamedWindow("Printout Pixel Value", 1);
cvShowImage("Printout Pixel Value", firstImage);
cvWaitKey(0); // 키 입력을 기다리게 하여 결과를 확인한다.
cvDestroyWindow("Printout Pixel Value");
cvReleaseImage(&firstImage); // 영상 해제
}
컬러의 경우 예>
마젠타(B채널과 R채널) 계열로 그래디언트 이미지를 생성하는 예제
void jvPrintoutColorPixelValue() {
int nWidth=256; int nHeight=256;
IplImage* firstImage = cvCreateImage( cvSize(nWidth,nHeight), IPL_DEPTH_8U, 3);
int x,y;
for(y=0;y<nHeight;++y)
for(x=0;x<nWidth;++x)
{ cvSet2D( firstImage, y, x, cvScalar(y,0,y,0)); }
for(x=0;x<nWidth;++x)
{
CvScalar v = cvGet2D( firstImage, y, x );
cout<<" B:"<<v.val[0];
cout<<" G:"<<v.val[1];
cout<<" R:"<<v.val[2];
cout<<endl;
}
OpenCV의 영상 데이터 구조체인 IplImage는 imageData 라는 배열에 영상의 픽셀 정보를 저장한다.
IplImage로부터 픽셀값을 읽어오는 방법에는 직접 imageData라는 배열에 접근해서 가져오는 방법과 cvGet* 함수를 이용하는 방법이 있다.
imageData
에 직접 접근하는 경우 픽셀의 인덱스를 계산하여 값을 얻어온다. 좌표가 (x,y)인 픽셀의 값은 다음과 같이 읽어온다. 인덱스를
계산할 때는 width 보다는 widthStep을 이용하는 것이 안전하다. widthStep은 IplImage에서 row하나가
저장되는 메모리의 폭을 나타낸다고 보면 된다. 영상의 폭이 홀수로 끝나는 경우는 영상의 한 row가 저장되는 메모리의 폭이
width와 다른 경우도 있기 때문이다. (이런 경우 x 좌표가 하나씩 밀리는 현상이 나타난다.)
다음과 같은 영상이 있다고 할 때,
IplImage *image = cvLoadImage(filename) ;
1 channel gray 영상의 경우:
int index = x + y*image->widthStep ;
unsigned char value = image->imageData[index] ;
3 channel RGB 영상의 경우:
int index = 3*x + y*widthStep ;
unsigned char B = image->imageData[index] ;
unsigned char G = image->imageData[index+1] ;
unsigned char R = image->imageData[index+2] ;
또
다른 방법은 OpenCV가 제공하는 cvGet 함수를 이용하는 것이다. 1채널 Gray 영상에는 cvGetReal2D()
함수를, 다채널 영상에는 cvGet2D()를 사용한다. cvGetReal2D 함수는 double 값을, cvGet2D는
CvScalar 구조체에 각 채널별로 값을 저장해서 리턴한다. 함수의 인자에 x와 y의 순서에 주의한다.
1 channel gray 영상의 경우
double value = cvGetReal2D(image, y, x) ;
3 channel RGB 영상의 경우
CvScalar value = cvGet2D(image, y, x) ;
double B = value.val[0];
double G = value.val[1];
double R = value.val[2];
imageData
에 직접 접근하는 방법이 픽셀값에 접근하는 성능이 빠르다고 한다. 아마도 cvGet* 함수 내부에 있는 오류 검출 코드들 때문이
아닌가 한다. 그렇지만 인덱스 계산할 필요 없이 픽셀 좌표 만으로 데이터를 가져오는 데는 cvGet* 함수가 더 편리하고, 코드도
읽기가 편하다. 성능에 문제가 없다면 괜찮은 방법이다.
decoding.exe의 0x00413f9d에 처리되지 않은 예외가 있습니다. 0xC0000005: 0x00000000 위치를 기록하는 동안 액세스 위반이 발생했습니다. 엑세스 위반이라는 것으로 실행이 안되는데요.
0x00413f9d 라는 부분은 어떻게 해석을 해야 할지와 엑세스 위반이라는 것은 무엇인지
답변 부탁드리겠습니다~(__)
답변
엑세스 위반(Access Violation)은, 프로세스가 접근할 권한이 없는 메모리 영역에 접근하고자 했을 때 발생합니다.
윈도우는 메모리의 모든 영역을 가상메모리 페이지 단위로 관리하는데, 이 메모리 영역마다 접근 권한을 설정합니다. 이 권한은
프로세스가 가진 속성별로 관리됩니다. 크게 사용자가 만든 사용자 프로세스와, 윈도우 시스템에서 돌리는 커널 프로세스로 구분할 수
있죠. 윈도우에서 관리하는 메모리는 커널에서만 읽고 쓸수 있는 영억, 커널에서만 읽을 수 있는 영억, 사용자프로세스에서도 읽을 수
있는 영역, 사용자 프로세스가 읽고 쓸 수 있는 영역 등으로, 가상메모리 페이지에 권한이 설정되어 있습니다.
따라서 적절한 권한이 없는 프로세스가 제한된 메모리 영역에 읽고 쓰기를 시도할 때 엑세스 위반이 발생합니다. 대표적인 예로
"0xC0000005: 0x00000000 위치를 기록하는 동안 엑세스 위반이 발생했습니다"라는 예외는 0x00000000 번지,
즉 널 포인터에 대하여 쓰기를 시도했다는 뜻이죠. 0x00000000번지로 시작하는 메모리 영역은 유저 프로세스에 대해서는
읽기/쓰기가 금지된 영역입니다. 시스템 운영에 중요한 정보가 기록되어 있기 때문이죠. 따라서 사용자가 만든 프로그램이 이 위치를
접근하고자 하는 것은 이와 같은 에러를 냅니다.
에러 메시지에서, "decoding.exe의 0x00413f9d에 처리되지 않은 예외가 있습니다. 0xC0000005:
0x00000000 위치를 기록하는 동안 액세스 위반이 발생했습니다."의 0x00413f9d는, 0x00000000위치를
접근하고자 하는 명령이 수행된, 명령이 저장된 번지수입니다. 디버깅할 때 해당 번지수의 명령이 무엇인가를 살펴보면 비교적 헤메지
않고 버그를 잡을 수 있습니다.
답변
이런 경우는 대부분..
메모리 할당이 되지 않은 포인터(NULL Pointer)에 값을 넣었거나..
할당치를 초과하여 데이터가 입력되었을 때입니다..
답변
0x00413f9d 는 decoding.exe 이란 프로그램의 code 영역 주소입니다.
즉 decoding.exe의 0x00413f9d 주소에서 예외처리가 발생했다는것입니다.
프로그램은 대부분 0x00400000이 시작번지이므로
offset 0x00013f9d에서 0xc0000005 에러가 발생했다는것입니다.
답변
0x00413f9d는 메모리의 주소를 말하는 것으로, 동적 메모리를 할당하지 않았거나
할당항 메모리를 넘어서 접근 할 경우에 이러한 에러가 나게 됩니다.
int *temp;
temp[0] = 10; // 메모리 에러(할당하지 않고 사용한 경우)
int *temp1;
temp1 = new int [10];
temp1[15] = 10; //메모리 에러(할당은 하였지만 할당항 메모리를 넘어서 사용한 경우)
CvMemBlock* top; /* Current memory block - top of the stack. */
struct CvMemStorage* parent; /* We get new blocks from parent as needed. */
int block_size; /* Block size. */
int free_space; /* Remaining free space in current block. */
}
CvMemStorage;
typedef struct CvMemBlock
{
struct CvMemBlock* prev;
struct CvMemBlock* next;
}
CvMemBlock;
2. deque
Deque = Double Ended Queue
우리말로 읽을때 발을을 "데크"라 발음한다.
Deque는 vector와 매우 유사하다. 데이터를 일렬로 저장하게 되며, 각 데이터에 대한 임의 접근이 가능하다. 연속적인 데이터의 끝에 요소를 추가하거나 삭제 하는 것은 constance time 즉 O(1) 이 걸린다. 또한 요소들의 중간에 데이터를 삽입하거나 삭제할 경우 linear time 즉 O(n) 의 time complexity 를 갖는다.
Deque
가 vector와 다른 가장 큰 이유는, deque는 sequence 즉 데이터들의 앞쪽에 데이터를 추가하거나 삭제하는데 있어서
constant time O(1)의 시간이 걸린다는 것이다. (*참고적으로 vector는 앞쪽에 데이터를 추가/삭재에 있어서
O(n) 의 시간이 걸린다).
3.cvFindContours - 외곽선생성
int cvFindContours(
CvArr* image, CvMemStorage* storage, CvSeq** first_contour, int
header_size=sizeof(CvContour), int mode=CV_RETR_LIST, int
method=CV_CHAIN_APPROX_SIMPLE, CvPoint offset=cvPoint(0,0) );
외부 Contour: 흑=>백
내부 Contour: 백=>흑
image
source이미지. 8-bit 1-channel. Gray 영상도 가능(non-zero: 1, zero: 0으로 간주함).
주. 함수의 수행 과정에서 원본 이미지를 변경시킴.
storage
찾아진 contour들을 저장할 container
first_contour
결과 매개변수. 첫 번째 외부 contour의 포인터 저장
header_size
결과는 CvSeq에 저장되는데 이 때 저장될 자료구조의 크기. - method가 CV_CHAIN_CODE이면 >= sizeof(CvChain) - 그렇지 않으면 >= sizeof(CvContour)
mode
추출 방법 - CV_RETR_EXTERNAL: 최외부 contour만 추출 - CV_RETR_LIST: 모든 contour를 찾아 한 list에 저장 (hierachycal 구조가 아님) - CV_RETR_CCOMP: 모든 contour를 찾아 2-level 트리 구조로 구성. top 레벨은 외부 contour, second 레벨은 내부contour(holes) - CV_RETR_TREE: 모든 contour를 찾아 full hierachy 트리 구조 구성 - CV_RETR_RUNS: Run-type 방법으로 추출하는 듯. 자세히는 모르겠음.
method
Approximation 방법 (CV_RETR_RUNS는 자체 방법을 사용하므로 이를 제외한 모든 방법에서만 사용함) - CV_CHAIN_CODE: the Freeman chain code로 저장됨. (이걸 제외하고 나머지 method는 polygon 구조(sequences of vertices)로 저장함. - CV_CHAIN_APPROX_NONE: 단순히 chain code를 point 구조로 변경함 - CV_CHAIN_APPROX_SIMPLE: 수평, 수직, 대각선 성분을 압축. ending point들만 남겨둠. - CV_CHAIN_APPROX_TC89_L1, CV_CHAIN_APPROX_TC89_KCOS: Teh-Chin chain approximation 알고리즘으로 압축 - CV_LINK_RUNS: CV_RETR_LIST 모드에서만 사용됨. 자세히는 모르겠음.
offset
contour 포인트들이 shift될 값 ROI 설정한 경우 좋음.
cvFindContours 함수는 이미지에서 contour를 찾은 후 contour의 갯수를 리턴
원형
int cvFindContours( CvArr* image, CvMemStorage* storage, CvSeq* first_contour,
int header_size=sizeof(CvContour), int mode, int mthod, CvPoint offset=cvPoint(0,0) );
패러미터 - image : 8비트 grayscale 영상만을 패러미터로 넘길 수 있다. 0이 아닌 픽셀은 1로, 0인 픽셀은 0으로 간주하여 자체적으로 2진 영상으로 처리하게 된다.
- storage : CvMemStorage 구조체의 포인터를 넘긴다. 실제로 contour 정보가 저장되는 공간이다.
- first_contour : 첫번째 최외곽 contour의 정보를 저장할 CvSeq 구조체의 포인터
- header_size : 시퀀스 헤더의 크기 ex) sizeof(CvContour)
- mode : contour 검출 방법
CV_RETR_EXTERNAL - 오직 최외곽 contour만을 검출한다.
CV_RETR_LIST - 모든 contour를 검출하여 list로 저장한다.
CV_RETR_CCOMP - 모든 contour를 검출한 후 2단계로 계층화한다.
상위 계층은 각 성분(component)의 외곽 경계를 나타내며 하위 계층은 성분 내부의
구멍(hole)을 나타낸다.
CV_RETR_TREE - 모든 contour를 검출한 후 각 contour를 tree 형태로 구조화한다.
- moethod : approximation(생략산) 방법이다.
생략산이란 불필요한 연산량을 줄이기 위하여 모든 contour를 구하지 않고 추정
CV_CHAIN_CODE - Freeman chain code를 사용하여 contour를 출력한다. 이것 외의 다른 모든 방법은
결과물을 polygon(다각형, 버텍스의 나열)로 출력한다.
CV_CHAIN_APPROX_NONE - 모든 contour의 점을 chain code에서 point로 변환한다.
CV_CHAIN_APPROX_SIMPLE - 가로, 세로, 대각선 으로 쪼개서 압축하며 함수는 끝점만을 출력한다.
CV_CHAIN_APPROX_TC89_L1, CV_CHAIN_APPROX_TC89_KCOS - Teh-Chin chain approximation
알고리즘에 사용된다.
CV_LINK_RUNS - 1의 수평 구획의 연결을 통해 완전히 다른 방식의 contour 검출 알고리즘을 사용한다.
CV_RETR_LIST 모드에서만 사용 가능하다.
- offset : 모든 contour 포인트 이동
예제
CvMemStorage* storage = cvCreateStorage(0); // 배열 자료형 : 점의 좌표가 들어감
CvSeq* seq = NULL; //경계 개수를 저장할 변수 선언
int numContour = cvFindContours(src, storage, &seq, sizeof(CvContour), CV_RETR_TREE,
CV_CHAIN_APPROX_SIMPLE);