2021 -1 영상신호처리
1. image Moment Invariant Function
- MIF에 대한 간단한 설명과 작성한 코드
- 4개의 클래스, 총 12개의 이미지에 대해 MIF 코드를 적용한 결과
- 엑셀을 이용한 클래스 분류 결과
- 새로운 영상을 생성해 클래스를 분류한 결과 (중요)
- principal axis 각도 계산
- 원본이미지로부터 수평 각도 계산하는 코드
- 그에 대해 이미지를 회전 시켜 원본을 찾게 해주는 코드
- discussion
각도 구하는 함수의 논의 - 이미지를 어떻게 돌려야 하는가?
MIF란 이미지에 고유한 밝기 중심점(invariant moment) 이 있다는 특성을 이용해 동일한 물체들을 분류하는 방법이다. 마치 물리세계의 물체들이 고유한 질량 중심을 가지고 있듯, 이미지 또한 밝기 중심점을 가지고 있으며 물체의 확대나 회전에 변하지 않는 성질이므로 이를 이용해 같은 물체를 분류할 수 있음을 새롭게 배우게 되었다. 수업을 듣고 난 후 Image Moment에 대해 조금 더 자세히 알아보고 싶어져, Wikipedia에서 ‘Image Moment’ 설명 자료를 참고해 추가적으로 공부하는 시간도 가졌다. 공부하면서 알게 된 내용은 다음과 같다.
이렇게 개인적으로 공부해보는 시간을 통해, 이번 과제에서 다루는 내용이 결국 이미지의 모먼트를 이용해 이미지의 ‘Hu moment invariant’ 를 구하는 과정임을 이해하게 되었다. 내용을 확실히 이해하고 과제에 임하니, 코드를 작성하는 과정이 수월했다. 앞으로도 추가적으로 이미지 처리기법에 대해 미리 공부해 보아야겠다는 생각도 들었다.
주어진 과제의 내용을 구현하기 위해 나는 다음과 같은 코드를 먼저 구상해보았다.
double calculateMpq(BYTE **img, int w, int h, int p, int q)
물체의 Raw Moment를 구하는 코드로, 함수 return 인자의 타입을 double로 주어, 계산된 Mpq의 Return 하도록 코드를 작성하면 좋겠다는 생각이 들어, 위와 같이 작성하였다.
calculateMupq(BYTE **img, int w, int h, int p, int q, double x0, double y0, int m00)
물체의 Central Moment를 구하는 코드로, 마찬가지로 함수 return 인자의 타입을 double로 주었다. 계산된 Mupq의 값을 Return 하도록 코드를 작성하면 좋겠다는 생각이 들어서였다. 또한 파라미터 x0, y0는 이미지의 질량 중심 값을 의미한다. 이 값은 위의 calculateMpq 함수가 선행되어야만 대입 가능하다.
void Compute_Moment_Invariant_Function(BYTE **img, int w, int h, double &M2, double &M3)
마지막으로 위의 subroutine을 통해 구한 Raw, Central Moment를 바탕으로 2차 M2와 3차 특성 M3를 구하는 함수이다. 이 함수에서는 직접 M2와 M3 값을 바꿔서 사용하는 함수이기 때문에, 특별히 반환할 내용이 없어 void 함수로 지정했다.
이렇게 코드를 작성해 보기로 구상했고, 이를 바탕으로 짠 코드는 다음과 같다.
//p,q 차 raw Moment를 구하는 과정
double calculateMpq(BYTE **img, int w, int h, int p, int q)
{
double mpq = 0.;
// 반환값으로 돌려줄 Central Moment 변수를 먼저 선언해준다. p,q차 raw moment 라는 뜻이다.
int x, y;
//계산의 기준이 되는 이미지의 픽셀을 탐색하기 위한 변수, x, y를 선언해준다.
for(y=0; y<h; y++)
{
for(x=0; x<w; x++)
{
mpq += pow((double)x, p)*pow((double)y, q)*(double)img[y][x];
// 주어진 공식을 바탕으로, raw Moment 의 값을 계산해 return parameter인 mpq에 대입한다.
}
}
return mpq;
}
// p,q차 Central Moment를 구하는 과정
double calculateMupq(BYTE **img, int w, int h, int p, int q, double x0, double y0, int m00) {
double mupq = 0.;
// 반환값으로 돌려줄 Central Moment 변수를 먼저 선언해준다. p,q차 central moment 라는 뜻이다.
int x, y;
//계산의 기준이 되는 이미지의 픽셀을 탐색하기 위한 변수, x, y를 선언해준다.
for(y=0; y<h; y++) {
for(x=0; x<w; x++) {
mupq += pow((x - x0), p) * pow((y - y0), q) * (double)img[y][x];
//주어진 공식을 바탕으로, central Moment 의 값을 계산해 return parameter인 mupq에 대입한다.
//이 때 x0, y0의 값은 이미지의 밝기 중심점을 의미하며,
// calculateMpq 함수의 계산 값으로 부터 계산되어지는 값이다.
}
}
mupq = mupq / m00;
//위 과정은, m00(p=q=0 인 Raw moment)로 central Moment를 Normalize 해주는 과정이다.
return mupq;
}
// Compute_Moment_Invariant_Function 의 정의
void Compute_Moment_Invariant_Function(BYTE **img, int w, int h, double &M2, double &M3) {
int x, y;
double x0, y0;
double m00 = calculateMpq(img, w, h, 0, 0);
double m10 = calculateMpq(img, w, h, 1, 0);
double m01 = calculateMpq(img, w, h, 0, 1);
//각각 00차, 10차, 01차 Raw Moment를 작성한 서브루틴을 이용해 구해준다.
x0 = m10 / m00;
y0 = m01 / m00;
//위에서 구한 Raw Moment를 토대로, 이미지의 밝기 중심점 x0, y0의 값을 찾아준다.
double u11 = calculateMupq(img, w, h, 1, 1, x0, y0, m00);
double u20 = calculateMupq(img, w, h, 2, 0, x0, y0, m00);
double u02 = calculateMupq(img, w, h, 0, 2, x0, y0, m00);
double u30 = calculateMupq(img, w, h, 3, 0, x0, y0, m00);
double u12 = calculateMupq(img, w, h, 1, 2, x0, y0, m00);
double u21 = calculateMupq(img, w, h, 2, 1, x0, y0, m00);
double u03 = calculateMupq(img, w, h, 0, 3, x0, y0, m00);
// 물체의 2차, 3차 Invariant Moment를 계산하기 위해 필요한 일부 central 모먼트를 구해준다.
// 마찬가지로 내가 작성한 서브루틴을 사용하였다.
double r = sqrt((u20 + u02));
M2 = ((u20 - u02)*(u20 - u02) + 4*u11*u11) / pow(r, 4);
M3 = ((u30 - 3*u12)*(u30 - 3*u12) + (3*u21 - u03)*(3*u21 - u03)) / pow(r, 6);
}
위 함수들을 바탕으로 이미지의 Invariant Moment를 출력한 결과는 다음과 같다.
각 이미지의 밑 부분에 떠있는 숫자들이 M2, M3 image invariant funtion의 결과값이다. 값을 자세히 확인해 보면, 동일한 물체 들끼리는 M2, M3 값이 비슷한 것을 확인할 수 있다. 정말 같은 물체끼리는 확대, 축소를 시키더라도 이 값이 유사한지를 확인해 보기 위해 그래프를 그려보았다.
현재는 배경과 물체의 segmentation이 제대로 이루어진 이미지를 사용해서 invariant Moment의 계산 결과가 좋게 나왔다는 생각이 들었다. 그래서 ‘image segmentation’에 대해서도 흥미가 가게 되었고, 이를 잘 수행하기 위해 어떻게 해야 할지 고민해보게 되었다. 이번 과제에선 image segmentation을 직접적으로 수행해보진 않았으나, 기회가 된다면 꼭 도전해보고 싶다.
어떤 이미지가 원본 이미지로부터 theta 만큼 회전 되었을 때, Central moment를 가지고 물체의 회전된 각도를 파악할 수 있다는 원리를 알게 되었다. 이 또한 실제 코드로 구현하기 위해, 다음과 같은 코드 구상을 떠올려 보았다.
calculateMupq(BYTE **img, int w, int h, int p, int q, double x0, double y0, int m00)
물체의 Central Moment를 구하는 과정이 선행되어야 하므로, 이 서브루틴을 다시 활용할 예정이다.
void Compute_Paxis(BYTE **img, int w, int h, double &theta)
위에서 구한 4개의 Central Moment 값을 토대로, theta 값을 구하는 코드 형태로 짜볼 예정이다. 또한 최종 구해진 theta 값은 radian의 형태이기 때문에, 180/PI를 곱해서 출력시킬 것이다. 그렇게 하면 angle의 형태로 보기 편하게 나올 것이다.
void RotateImage(BYTE **img1, BYTE **img2, int w, int h, double &theta)
입력 받은 theta를 토대로, 이미지를 theta 만큼 다시 회전시켜 원본 이미지를 출력하기 위한 코드이다. 영상 신호처리 실습 때 구현했던 내용이라 어렵지 않게 구형할 수 있었다. 다만 Compute_Paxis 함수를 통해 구해진 theta는 (-) 값으로 나오기 때문에, 이를 다시 양의 값으로 보정해주는 과정이 필요하겠다.
이를 바탕으로 작성한 코드는 다음과 같다.
// 원본이미지로부터 수평 각도 계산하는 코드
void Compute_Paxis(BYTE **img, int w, int h, double &theta)
{
int x, y; // 이미지 픽셀 좌표를 조회하기 위한 변수
double x0, y0; // 이미지의 밝기 중심을 의미하는 변수
double m00 = calculateMpq(img, w, h, 0, 0);
double m10 = calculateMpq(img, w, h, 1, 0);
double m01 = calculateMpq(img, w, h, 0, 1);
x0 = m10 / m00;
y0 = m01 / m00;
double u20 = calculateMupq(img, w, h, 2, 0, x0, y0, m00);
double u02 = calculateMupq(img, w, h, 0, 2, x0, y0, m00);
double u11 = calculateMupq(img, w, h, 1, 1, x0, y0, m00);
theta = atan(2*u11 / (u20 - u02)) / 2;
//공식에 똑같이 대입하여, radian의 개형으로 나오는 theta를 구한다.
}
//그에 대해 이미지를 회전 시켜 원본을 찾게 해주는 코드
void RotateImage(BYTE **img1, BYTE **img2, int w, int h, double &theta)
{
int x, y, xp, yp, cx, cy;
cx = (int)(w*0.5);
cy = (int)(h*0.5);
for(y=0; y<h; y++) {
for(x=0; x<w; x++) {
xp = (int)((x-cx)*cos(-theta) + (y-cy)*sin(-theta)) + cx;
yp = (int)(-(x-cx)* sin(-theta) + (y-cy)*cos(-theta)) + cy;
// theta 값을 -로 넣어주지 않으면, 원본 이미지에 대해 회전된 이미지가 +theta (반시계방향) 이 아닌
// 시계 방향으로 theta 만큼 이동했다고 생각해 이미지를 시계 방향으로 theta 만큼 더 회전 시켜버릴 것이다.
// 그래서, theta를 (-) 로 보정해주는 과정이 필요하다.
if (xp>=0 && xp< w && yp>=0 &&yp<h) img2[y][x] = img1[yp][xp];
else img2[y][x] =0;
}
}
}
위의 코드를 실행한 출력 화면은 다음과 같다.
다음과 같이 이미지가 회전된 각도를 Central Moment를 통해 구할수 있으며, 이를 이용해 이미지 재구성 가능성도 확인해볼 수 있었다.
이미지 회전 각도, theta를 알게 되었을 때 이를 이용해 어떻게 이미지를 회전 시켜 원본 이미지를 찾아야 하는지에 대한 고민을 했었다. 그래서 처음엔 theta = –36.10도를 그대로 사용해, 이미지가 시계방향으로 36.10도 만큼 더 틀어져 버리는 엉뚱한 결과를 얻기도 했었다. 하지만 RotateImage
함수의 원리를 생각해보니, 곧바로 코드를 수정할 수 있었다.
Compute_Paxis
함수의 원리는 회전된 이미지로부터 원본 이미지를 구성하려면 반시계 방향으로 얼마나 theta를 돌려야 하는지에 대한 값을 알려준다. 그래서 위의 과정에서는 반시계방향으로 –36.10도 만큼 돌려야 한다는 값을 알려준 것이다.
RotateImage
함수의 원리는, 새로 구성하는 img2가 원본 이미지라고 가정하고, 그에 대해 원래 있었던 회전된 이미지, img1이 반시계방향으로 theta 만큼 틀어져 있다고 판단한다. 그래서 img1을 시계 방향으로 theta 만큼 이동시킨 이미지를 img2로 처리한다.
위에서 도출된 theta의 값은 –36.10도 였으므로, RotateImage 함수에 그냥 theta를 넣는다면 시계 방향으로 –36.10도 만큼, 즉 반시계방향으로 36.10도 만큼 회전시킬 것이다. 고로 이미지가 더 틀어지게 된다. 이를 보정하기 위해선 theta를 (-)로 처리하여, 코드의 충돌이 없게 만들어야 한다.