2023. 5. 25. 09:58ㆍComputer/영상처리
특징값(feature) : 영상 내에서 다른 부분과 구분되어 두드러지는 성질
-> 점, 엣지(경계선, 외곽선), 코너(모서리), 질감, 색상
특징값은 영상 내에서 특정 사물의 위치를 찾는데 사 용될 수 있고, 특정 영상과 다른 영상의 유사성을 판단 하는 기준으로도 사용 될 수 있다.
↓ 엣지(edge) : 배경과 객체의 경계가 되는 부분으로, 일반적으로 엣지에서 는 픽셀의 밝기 값이 급격하게 변함
마스크를 이용한 엣지 검출
엣지와 함수 그래디언트
영상에서 엣지를 검출하는 방법 : 영상을 미분한 후, 미분 값이 특정 임계값(threshold)보다 큰 값을 엣지로 한다.
미분은 기본적으로 연속 함수에 대해 정의된 수학적 개념이므로 이산 함수에 해당하는 영상에 적용하기 위해서는 미분 함수를 근사화하여 사용해야 한다. 일반적으로 아래의 세 가지 미분 근사(approximation) 방법이 사용되고 있다.
1차 미분의 근사화의 3가지 방법
미분 근사 방법을 영상에 적용할 경우 I(x, y)는 영상과 같은 2차원 이산 함수를 나타내고, h는 이산값의 간격이다. h는 픽셀의 간격 1로 간주할 수 있다. 중간값 차이를 이용하는 방법이 이론적으로 근사화 오류가 가장 적기 때문에 마스크 연산시 이용한다.
영상의 그래디언트(gradient) : 함수 f(x, y)를 x축과 y 축으로 각각 편미분(partial derivative) 하여 벡터 형태로 표현한 것
그래디언트 벡터의 방향이 변화의 정도가 가장 심한 방향이고, 그래디언트 벡터의 크기는 변화의 정도를 나타내는 척도이다. 즉, 영상에서 엣지 위치를 찾기 위해서는 그래디언트 벡터의 크기가 임계값(threshold)보다 큰 경우에 엣지로 정의한다.
마스크 기반 엣지 검출
이산 함수의 미분 근사 방법을 이용한 다양한 엣지 검출 마스크
void IppEdgeRoberts(IppByteImage& img, IppByteImage& imgEdge)
{ //타겟 픽셀 1
int w = img.GetWidth();
int h = img.GetHeight();
imgEdge.CreateImage(w, h);
BYTE** p1 = img.GetPixels2D();
BYTE** p2 = imgEdge.GetPixels2D();
int i, j, h1, h2;
double hval;
for (j = 1; j < h; j++)
for (i = 1; i < w - 1; i++)
{
h1 = p1[j][i] - p1[j - 1][i - 1];
h2 = p1[j][i] - p1[j - 1][i + 1];
hval = sqrt(static_cast<double>(h1 * h1 + h2 * h2)); //그래디언트 크기 계산
p2[j][i] = static_cast<BYTE>(limit(hval + 0.5));
}
}
로버츠 마스크는 서로 대각선 위치에 있는 픽셀끼리의 차분 형태로 구성되어 있으며, 프리윗과 소벨 마스크는 가로 방향과 세로 방향의 엣지를 구하는 필터의 조합으로 구성되어 있다.
마스크를 이용한 엣지 검출의 단점 -> 엣지 주변의 여러 픽셀들이 한꺼번에 엣지로 검출, 엣지가 이어지지 않고 끊어지는 부분이 있음
캐니 엣지 검출기
좋은 엣지 검출기의 조건 (J. Canny)
- 정확한 검출(Good detection): 엣지가 아닌 점을 엣지로 찾거나 또는 엣지인 점을 검출하지 못하는 확률을 최소화해야 한다.
- 정확한 위치(Good localization): 실제 엣지의 중심을 찾아야 한다.
- 단일 엣지(Single edge): 하나의 엣지는 하나의 점으로 표현되어야 한다.
캐니 엣지 검출기 특징
- 그래디언트의 크기 뿐만 아니라 방향을 모두 고려하여 엣지의 위치를 정확하게 찾는다.
- 엣지의 진행 방향에 엣지가 또 나타날 수 있다는 점을 예상하여 그래디언트가 약하게 나타나는 엣지픽셀도 검출한다.
캐니 엣지 검출 방법
1. 가우시안 필터링
영상에 있는 백색잡음을 제거하기 위해 가우시안 필터를 적용하는 것이 가장 효과적이다.
2. 그래디언트 계산(크기-전체 계산 & 방향-일부 필요한 픽셀만)
그래디언트 계산은 보통 소벨 마스크를 사용한다. 가로 방향으로의 미분을 계산하는 소벨 마스크 결과 영상을 Gx라고 하고 세로 방향으로의 미분을 계산하는 소벨 마스크 결과 영상을 Gy라고 할 경우
3. 비최대 억제(그래디언트 방향의 수직이 엣지)
비최대 억제는 여러 개의 픽셀에 의해 하나의 엣지가 표현되는 현상을 없애기 위하여 그래디언트 크기가 국지적 최대인 픽셀만을 엣지 픽셀로 설정하는 기법이다.
('국지적 최대'는 경계 픽셀을 찾는 과정 중에서, 픽셀의 경계 강도가 주변 픽셀보다 최대인지 확인하는 것)
국지적 최대 판별하는법 -> 그래디언트의 방향 성분을 고려하여 엣지의 방향에 수직인 방향으로 인접한 픽셀과 크기를 검사한다.
캐니 엣지 검출기에서 비최대 억제를 적용하기 위해 그래디언트 방향 성분을 크게 네 개의 구역으로 나눈다. 0~360° 범위의 각도 성분을 양자화하여 4개의 방향 성분으로 나눈다.
// 3. 비최대 억제
// 국지적 최대를 구함과 동시에 이중 임계값을 적용하여 strong edge와 weak edge를 구한다.
imgEdge.CreateImage(w, h);
BYTE** pEdge = imgEdge.GetPixels2D();
enum DISTRICT { AREA0 = 0, AREA45, AREA90, AREA135, NOAREA };
const BYTE STRONG_EDGE = 255;
const BYTE WEAK_EDGE = 128;
std::vector<IppPoint> strong_edges;
float ang;
int district;
bool local_max;
for (j = 1; j < h - 1; j++)
for (i = 1; i < w - 1; i++)
{
// 그래디언트 크기가 th_low보다 큰 픽셀에 대해서만 국지적 최대 검사
// 국지적 최대인 픽셀에 대해서만 강한 엣지 또는 약한 엣지로 설정
if (pMag[j][i] > th_low)
{
// 그래디언트 방향 계산 (4개 구역)
if (pGx[j][i] != 0.f)
{
ang = atan2(pGy[j][i], pGx[j][i]) * 180 / PI_F;
if (((ang >= -22.5f) && (ang < 22.5f)) || (ang >= 157.5f) || (ang < -157.5f))
district = AREA0;
else if (((ang >= 22.5f) && (ang < 67.5f)) || ((ang >= -157.5f) && (ang <
-112.5f)))
district = AREA45;
else if (((ang >= 67.5) && (ang < 112.5)) || ((ang >= -112.5) && (ang < -67.5)))
district = AREA90;
else
district = AREA135;
}
else
district = AREA90;
// 국지적 최대 검사
local_max = false;
switch (district)
{
case AREA0:
if ((pMag[j][i] >= pMag[j][i - 1]) && (pMag[j][i] > pMag[j][i + 1]))
local_max = true;
break;
case AREA45:
if ((pMag[j][i] >= pMag[j - 1][i - 1]) && (pMag[j][i] > pMag[j + 1][i + 1]))
local_max = true;
break;
case AREA90:
if ((pMag[j][i] >= pMag[j - 1][i]) && (pMag[j][i] > pMag[j + 1][i]))
local_max = true;
break;
case AREA135:
default:
if ((pMag[j][i] >= pMag[j - 1][i + 1]) && (pMag[j][i] > pMag[j + 1][i - 1]))
local_max = true;
break;
}
// 강한 엣지와 약한 엣지 구분
if (local_max)
{
if (pMag[j][i] > th_high)
{
pEdge[j][i] = STRONG_EDGE;
strong_edges.push_back(IppPoint(i, j));
}
else
pEdge[j][i] = WEAK_EDGE;
}
}
}
4. 이중 임계값을 이용한 히스테리시스 엣지 트래킹
캐니 엣지 검출기에서는 두 개의 임계값을 사용한다. 강한 엣지는 모두 최종적인 엣지 픽셀로 선택되지만, 약한 엣지는 선별하여 일부만 엣지로 판별한다. 이때 사용되는 방법이 히스테리시스 엣지 트래킹 방법이다.
히스테리시스 엣지 트래킹 방법은 엣지 픽셀이 대체로 상호 연결되어 있다는 점을 이용한다. 만약 약한 엣지로 판별된 픽셀이 강한 엣지와 서로 연결되어 있다면, 이 약한 엣지는 최종적으로 엣지 픽셀로 판별한다.
허프 변환을 이용한 직선 검출
허프 변환Hough transform 알고리즘 : 2차원 영상 좌표에서의 직선의 방정식을 파라미터 (parameter) 공간으로 변환하여 직선 성분을 찾는 알고리즘
영상에서 직선 성분을 찾기 위해서는 우선 엣지 픽셀의 위치를 찾아내고, 이러한 엣지 픽셀들이 직선의 방정식에 맞게 일렬로 배열되어 있는지를 확인해야 한다.
2차원 xy 공간에서의 직선의 방정식을 ab 공간으로 변형하면 2차원 xy 공간에서의 직선은 ab 공간에서 한 점으로 나타나고, 반대로 xy 공간에서 한 점은 ab 공간에서 직선의 형태로 나타난다. 허프 변환은 이러한 현상을 이용하여 xy 공간에서의 직선을 찾는다.
xy 공간에서 엣지로 판명된 모든 점의 좌표를 ab 파라미터 공간으로 변경하여 직선을 그려주고, 직선들이 많이 교차되는 점을 찾으면 된다. 이때 직선이 교차하는 점을 찾기 위해서 보통 축적 배열을 사용한다.
축적 배열이란 일반적인 2차원 배열의 형태이며, 직선이 지나가는 위치의 배열 원소의 값을 1씩 증가시킨다.
직선의 방정식 y = ax + b 를 사용하면 y축과 평행한 수직선을 표현하지 못함 ➔ 극좌표계(평면 위의 점을 거리와 각도로 나타냄) 형태의 직선의 방정식을 사용
해리스 코너 포인트 검출 방법
해리스 코너 검출 방법 : 작은 윈도우를 상하좌우로 움직이며 윈도우 안의 픽셀 값의 변화를 분석하여 영상에서 모서리 부분, 즉 코너 포인트(corner point)를 찾는 알고리즘
영상이 회전하거나 크기가 다소 변하더라도 코너 포인트는 동일한 위치에서 검출이 되기 때문에, 두 영상의 유사점을 찾기 위한 방법으로도 많이 사용되고 있다.
코너 포인트에 윈도우가 위치해 있는 경우 윈도우가 상하좌우 어느 방향으로 움직여도 그 안에 있는 값의 변화가 크게 나타날 것이라고 예상할 수 있다. 그러므로 이 위치를 코너 위치로 판별한다.
IppHarrisCorner 함수는 입력 영상과 동일한 크기의 동적 배열을 여러 개 만들어 사용하고 있다. imgDx2, imgDy2, imgDxy는 각각 Ix2, Iy2, IxIy를 나타낸다. imgDgdx2, imgDgdy2, imgDgdxy는 Ix2, Iy2, IxIy에 가우시안 필터링을 수행한 결과를 저장한다. imgCrf는 해리스 코너 응답 함수의 값을 저장한다.
//해리스 코너 검출 구현 함수
void IppHarrisCorner(IppByteImage& img, std::vector<IppPoint>& corners, double th){
//입력 영상, 검출된 코너 포인트 정보는 corners에 저장, th는 해리스 코너 응답 함수에서 코너 여부를 결정할 때 사용할 임계값
register int i, j, x, y;
int w = img.GetWidth();
int h = img.GetHeight();
BYTE** ptr = img.GetPixels2D();
/*-------------------------------------------------------------------------
1. (fx)*(fx), (fx)*(fy), (fy)*(fy) 계산
행렬 M에 정의되어 있는 Ix2, IxIy, Iy2 값을 계산하는 부분
-------------------------------------------------------------------------*/
IppFloatImage imgDx2(w, h);
IppFloatImage imgDy2(w, h);
IppFloatImage imgDxy(w, h);
float** dx2 = imgDx2.GetPixels2D();
float** dy2 = imgDy2.GetPixels2D();
float** dxy = imgDxy.GetPixels2D();
// 프리윗 마스크 형태의 미분 연산자를 사용
float tx, ty;
for (j = 1; j < h - 1; j++) {
for (i = 1; i < w - 1; i++)
{
tx = (ptr[j - 1][i + 1] + ptr[j][i + 1] + ptr[j + 1][i + 1]
- ptr[j - 1][i - 1] - ptr[j][i - 1] - ptr[j + 1][i - 1]) / 6.f;
ty = (ptr[j + 1][i - 1] + ptr[j + 1][i] + ptr[j + 1][i + 1]
- ptr[j - 1][i - 1] - ptr[j - 1][i] - ptr[j - 1][i + 1]) / 6.f;
dx2[j][i] = tx * tx;
dy2[j][i] = ty * ty;
dxy[j][i] = tx * ty;
}
}
/*-------------------------------------------------------------------------
2. 가우시안 필터링, 코너가 잡음때문에 제대로 안나올수있어서
5×5 크기의 근사된 형태의 가우시안 마스크, 가중 평균 값 필터에서 제시한 마스크와 동일한 형태이다.
-------------------------------------------------------------------------*/
IppFloatImage imgGdx2(w, h);
IppFloatImage imgGdy2(w, h);
IppFloatImage imgGdxy(w, h);
float** gdx2 = imgGdx2.GetPixels2D();
float** gdy2 = imgGdy2.GetPixels2D();
float** gdxy = imgGdxy.GetPixels2D();
float g[5][5] = { { 1, 4, 6, 4, 1 },{ 4, 16, 24, 16, 4 },
{ 6, 24, 36, 24, 6 },{ 4, 16, 24, 16, 4 },{ 1, 4, 6, 4, 1 } };
for (y = 0; y < 5; y++) {
for (x = 0; x < 5; x++)
{
g[y][x] /= 256.f;
}
}
float tx2, ty2, txy;
for (j = 2; j < h - 2; j++) {
for (i = 2; i < w - 2; i++) {
tx2 = ty2 = txy = 0;
for(y=0;y<5;y++)
for (x = 0; x < 5; x++) {
tx2 += (dx2[j + y - 2][i + x - 2] * g[y][x]);
ty2 += (dy2[j + y - 2][i + x - 2] * g[y][x]);
txy += (dxy[j + y - 2][i + x - 2] * g[y][x]);
}
gdx2[j][i] = tx2;
gdy2[j][i] = ty2;
gdxy[j][i] = txy;
}
}
/*-------------------------------------------------------------------------
3. 코너 응답 함수 생성
-------------------------------------------------------------------------*/
IppFloatImage imgCrf(w, h);
float** crf = imgCrf.GetPixels2D(); // 2차원 포인터 변수 crf에 응답 함숫값을 저장
float k = 0.04f;
for (j = 2; j < h - 2; j++) {
for (i = 2; i < w - 2; i++) {
crf[j][i] = (gdx2[j][i] * gdy2[j][i] - gdxy[j][i] * gdxy[j][i])
-k*(gdx2[j][i] + gdy2[j][i]) * (gdx2[j][i] + gdy2[j][i]);
}
}
/*-------------------------------------------------------------------------
4. 임계값보다 큰 국지적 최댓값을 찾아 코너 포인트로 결정
좌표 (i, j)와 주변 8픽셀에서의 해리스 코너 응답 함숫값을 비교하여
(i, j)에서의 함숫값이 가장 클 때에만 코너 포인트로 기록하게 한다.
-------------------------------------------------------------------------*/
corners.clear();
float cvf_value;
for (j = 2; j < h - 2; j++) {
for (i = 2; i < w - 2; i++) {
cvf_value = crf[j][i];
if (cvf_value > th) {
if (cvf_value > crf[j - 1][i] && cvf_value > crf[j - 1][i + 1] &&
cvf_value > crf[j][i + 1] && cvf_value > crf[j + 1][i + 1] &&
cvf_value > crf[j + 1][i] && cvf_value > crf[j + 1][i - 1] &&
cvf_value > crf[j][i - 1] && cvf_value > crf[j - 1][i - 1]) {
corners.push_back(IppPoint(i, j)); // 코너 포인트로 기록
}
}
}
}
}
'Computer > 영상처리' 카테고리의 다른 글
영상 날카롭게 만들기 (0) | 2023.06.07 |
---|---|
컬러 영상 처리 (0) | 2023.06.06 |
영상 새로 만들기 기능 구현(대화 상자 만들기) (0) | 2023.05.23 |
영상의 기하학적 변환 (0) | 2023.05.16 |
잡음 생성과 제거 (0) | 2023.05.09 |