본문 바로가기
Computer Vision

13. Canny Edge Detection (Bilinear Interpolation)

by 꿈꾸는 띵땅근 2021. 1. 22.
학습 내용
1. Bilinear Interpolation
2. Edge 방향 구하는법 / Non-Maxima Suppression
3. Canny Edge Operatior

 

 

1. Bilinear Interpolation


정확히 엣지 픽셀을 찾아내기란 어렵다. 엣지값을 정확하게 구하려면, Bilinear Interpolation이 필요할때가 있다. 

결국, 1,2,3,4 넓이를 이용하면 쉽게 구할 수 있다. 

 

 

 

 

 

 

 

2. Edge 방향 구하는법 / Non-Maxima Suppression


360도를 8등분해서 4방향만 있다고 생각하자.
그리고, Non-Maxima Suppression은 극댓값을 찾는 과정을 말한다. 

아래의 방식은 FDG를 적용한 이후부터를 말하는거다. 

가운데 동그라미 점이 양쪽 x보다 크면 극댓값이겠지

_igDir._ppA[i][j]는 바로 이전 사진에서 0~3으로 분류된 값이다. 

즉, 예를들어 _igDir._ppA[i][j]이 0이면 노란색 영역을 살펴볼텐데, 오른쪽 노란색과 왼쪽 노란색을 둘 다 보는게 위의 소스다. 

 

 

 

 

 

 

 

 

3. Canny Edge Operatior


<<캐니 엣지 검출 방법>>
해당 픽셀이 캐니 엣지인지 보려면, 그 픽셀 주변으로 (위에서 말한) 0~3 방향을 봤을때 

그래디언트가 일정 threshold(T1)를 넘으면 확실히 검출하고 
T1보다는 작지만, T2보다는 크면, 일단 후보군으로 넣어두고, 
T2보다 작으면 절대로 엣지가 아니라고 판단한다. 

캐니엣지는 엣지픽셀들끼리 잘 연결되지 않고, 뚝뚝 끊어지는 특징이 있다. 이것을 보완하기 위해 hysterisis-based thresholding 도입해서 애매한 녀석들을 만들었다. 

 

 

아래 소스의 nHalf는 gradient 판별 kernel의 크기의 절반을 의미한다. 

X방향, Y방향으로 우선 Gradient를 각각 구하고, 그것으로 1. Magnitude, 2. Orientation(방향, Angle), 3. Direction(0~3방향중 어느방향인지) 구한다.
Non Maxima Suppression.     kernel 크기의 절반만큼만 0~3방향의 픽셀들을 확인한다. 그래서 확연히 극대값이고, T1을 넘으면 oEdgeMag 라는 구조체로 _skTmp에 저장하고, 애매한 녀석들은 _idBuf로 정리한다. 

 

엣지로 확정난 픽셀들 주변 8방향에 애매한 녀석이 있다? 그러면 걔도 엣지로 추가해서 연속성을 조금이라도 높이자. 

void Jeong::CannyEdgeDetection(const KImageGray& igIn, KImageGray &igOut, int lowT, int highT)
{
    int nHalf = 1;
    int nRow = igIn.Row(), nCol = igIn.Col();
    int nSize = nRow * nCol;
    double dGradX, dGradY;
    double dSigma = 0.3;
    double d2Sigma = 2. * dSigma * dSigma;
    double dConstant = 1/(d2Sigma * _PI * dSigma * dSigma);
    double dScale = 0;
    int dx[4] = {1, 1, 0, -1};
    int dy[4] = {0, 1, 1, 1};
    int dx8[8] = {1, 1, 0, -1, -1, -1,  0, 1};
    int dy8[8] = {0, 1, 1, 1,  0,  -1, -1, -1};
    using namespace std;

    KImageGray igMag(nRow,nCol), igAng(nRow,nCol), igDir(nRow,nCol), igEdge(nRow,nCol), igBuf(nRow,nCol);
    KMatrix mKernelX(nHalf*2+1, nHalf*2+1), mKernelY(nHalf*2+1, nHalf*2+1);


    // FDG를 위한 masking kernel
    for(int i=-nHalf, ii=0; i<=nHalf; i++, ii++){ // i가 v
        for(int j=-nHalf, jj=0; j<=nHalf; j++, jj++){ // j 가 u
            mKernelY[ii][jj] = 1/(dConstant)*(-i)*exp(-i*i/(d2Sigma)) * exp(-j*j / (d2Sigma));
            mKernelX[jj][ii] = mKernelY[ii][jj];
            dScale += (i<0?mKernelY[ii][jj] : 0.0);
        }
    }
    // 이러면 이중포문 돌아서 모든 원소 나눗셈 해준다. operator 확인해봐라.
    mKernelX /= -dScale;
    mKernelY /= -dScale;

    // 1. Gradient Image 만들기 with FDG
    for(int i=nHalf; i<nRow-nHalf;i++)
        for(int j=nHalf; j<nCol-nHalf;j++){
            dGradX = dGradY = 0.0;
            for(int r = -nHalf, rr=0; r<=nHalf; r++, rr++)
                for(int c = -nHalf, cc=0; c<=nHalf; c++, cc++){
                    dGradX += igIn[i+r][j+c] * mKernelX[rr][cc]; // 가로방향
                    dGradY += igIn[i+r][j+c] * mKernelY[rr][cc]; // 세로방향
                }
            // magnitude
            igMag[i][j] = abs(dGradX)+abs(dGradY);

            // lowT 넘나?
            if(igMag[i][j]>lowT){
                igAng[i][j] = atan2(dGradY, dGradX) * (180.0/_PI)+ 0.5;  //(unsigned short)(oMath.Atan(dGradY, dGradX)+0.5);    이 씨발때문에... 존나 고생함..  // angle(DEG)
                igDir[i][j] = (unsigned char)((((int)(atan2(dGradY, dGradX) * (180.0/_PI)/22.5)+1)>>1) & 0x00000003);   // direction (RAD을 DEG로 바꿔서 계산)
//                igDir[i][j] = (unsigned char)((((int)(igAng[i][j]/22.5)+1)>>1) & 0x00000003); // 이거대로 하면 잘 안된다!!!!!
            }else{
                igMag[i][j]=0;
            }
        }

    //3.에서 pop용으로 쓰일 vector선언
    vector<EdgeInfo*> vtmp;

    //2. magnitude가 다 등록되었으로, Edge를 찾아서 등록하자
    for(int i=nHalf; i<nRow-nHalf; i++)
        for(int j=nHalf; j<nCol-nHalf; j++){
            if(igMag[i][j]==0)
                continue;
            else{ // 극대인지 check
                if(igMag[i][j]>igMag[i + dy[igDir[i][j]]][j + dx[igDir[i][j]]]
                        &&
                        igMag[i][j] > igMag[i - dy[igDir[i][j]]][j - dx[igDir[i][j]]]){
                    if(igMag[i][j] > highT){// 극대이면서 highT보다 높으면 Edge지.
                        igOut[i][j] = 255;

                        EdgeInfo* tmp = new EdgeInfo;
                        tmp->_nx = j;
                        tmp->_ny = i;
                        tmp->_ang = igAng[i][j]; // RAD
                        tmp->_dir = igDir[i][j];
                        tmp->_mag = igMag[i][j];
                        Edge.push_back(tmp);
                        vtmp.push_back(tmp); // 3.에서 pop용으로 만들었음.

                    }else{ // 극대이긴 하지만, highT보다 작으므로, 애매한 녀석으로 등록.
                        igBuf[i][j] = 255;
                    }
                }
            }
        }

    // 3. 애매한 녀석들 처리 -> Edge 주변에 애매한 녀석들이 있으면, 걔도 Edge로 등록
    while(!(vtmp.size()==0)){
        int i=vtmp.back()->_ny;
        int j=vtmp.back()->_nx;
        vtmp.pop_back();
        for(int k=0; k<8; k++){
            if(igBuf[i+dx8[k]][j+dy8[k]]==255){
                igBuf[i+dx8[k]][j+dy8[k]]=0;
                igOut[i+dx8[k]][j+dy8[k]]=255;

                EdgeInfo* tmp = new EdgeInfo;
                tmp->_nx = j+dy8[k];
                tmp->_ny = i+dx8[k];
                tmp->_ang = igAng[i+dx8[k]][j+dy8[k]]; // DEG
                tmp->_dir = igDir[i+dx8[k]][j+dy8[k]];
                tmp->_mag = igMag[i+dx8[k]][j+dy8[k]];

                Edge.push_back(tmp);
                vtmp.push_back(tmp);
            }
        }
    }

}

 

 

 

출처

댓글