SUSAN(Smallest Univalue Segment Assimilating Nucleus 最小核值相似区)算子不再沿用Harris算法的微分思想,通过统计圆形模板内符合要求的像素数目(本质是一个积分过程)检测角点,计算简便,抗噪声能力强,具有旋转和平移不变性。
SUSAN使用一个圆形的模板(图1-1),在这个模板内,比较每个点与中心点的相似度(为了使相似度的边界更平滑,一般用公式来近似,显然 t 越大得到的角点越少)。
图1-1. SUSAN 半径为3的圆形模板
统计模板内相似像素的个数。从图1-2很容易看到,当模板接近、进入、移出角点时,该统计值(也称USAN,核值相似区)会经历一个由大到小,再由小到大的过程,在角点处取最小值。将这个统计值与给定阈值(一般取n(r)的理论最大值(半径为3时为37-1)的一半,如果检测边缘则取其3/4)比较,若小于阈值,则处理后作为角点响应值(),最后做一个最大值抑制就可以了。
图1-2. USAN在a,d位置取最大值,在角点位置e取最小值
typedef struct myPoint { int x; int y; }_point; typedef struct mySize { int height; int width; }_size; void _cornerSUSAN(unsigned char* srcImg, _size srcSize, _point* corners, int* numCorner, float thresholdT); void getSUSANkernel(bool* kernel, int radius); void convolutionSUSAN(unsigned char* srcImg, float* R, _size srcSize, bool* kernel, int kernelSize, float thresholdT); void depressR(float* srcR, _point* dstR, int* numDst, _size imgSize, _size depressWin); void main() { cv::Mat img = cv::imread("../file/einstein.jpg", 0); unsigned char* imgBuffer = new unsigned char[img.rows*img.cols]; for(int i=0; i<img.rows; i++) { uchar* ptr = img.ptr<uchar>(i); for(int j=0; j<img.cols; j++) imgBuffer[i*img.cols+j] = ptr[j]; } _size srcSize; srcSize.height = img.rows; srcSize.width = img.cols; _point* corners = new _point[srcSize.height*srcSize.width]; int numCorners = 0; _cornerSUSAN(imgBuffer, srcSize, corners, &numCorners, 40); cv::Mat colorImg; cv::cvtColor(img, colorImg, CV_GRAY2BGR); for(int i=0; i<numCorners; i++) { cv::Point pt(corners[i].x, corners[i].y); cv::circle(colorImg, pt, 1, cv::Scalar(0,0,255)); } cv::namedWindow("show"); cv::imshow("show", colorImg); cv::waitKey(0); } void _cornerSUSAN(unsigned char* srcImg, _size srcSize, _point* corners, int* numCorner, float thresholdT) { int kernelSize = 3; bool* kernel = new bool[(2*kernelSize+1)*(2*kernelSize+1)]; getSUSANkernel(kernel, kernelSize); float* R = new float[srcSize.height*srcSize.width]; convolutionSUSAN(srcImg, R, srcSize, kernel, kernelSize, thresholdT); delete[] kernel; _size depressSize; depressSize.height = 3; depressSize.width = 3; depressR(R, corners, numCorner, srcSize, depressSize); delete[] R; } void getSUSANkernel(bool* kernel, int radius) { for(int i=0; i<2*radius+1; i++) { for(int j=0; j<2*radius+1; j++) { float realRadius = sqrtf((i-radius)*(i-radius)+(j-radius)*(j-radius)); int intRadius = int(realRadius+0.5); if(intRadius<=radius) kernel[i*(2*radius+1)+j] = true; else kernel[i*(2*radius+1)+j] = false; } } } void convolutionSUSAN(unsigned char* srcImg, float* R, _size srcSize, bool* kernel, int kernelSize, float thresholdT) { for(int i=0; i<srcSize.height; i++) { for(int j=0; j<srcSize.width; j++) { float sumValue = 0; int count = 0; for(int m=-kernelSize; m<=kernelSize; m++) { for(int n=-kernelSize; n<=kernelSize; n++) { int indexX = j+n; int indexY = i+m; if(indexX>=0 && indexX<srcSize.width && indexY>=0 && indexY<srcSize.height) { if(kernel[(m+kernelSize)*(2*kernelSize+1)+(n+kernelSize)]) { int mValue = int(srcImg[i*srcSize.width+j]) - int(srcImg[indexY*srcSize.width+indexX]); float fValue = -1*pow(mValue/thresholdT, 6); float eValue = exp(fValue); sumValue += eValue; count++; } } } } R[i*srcSize.width+j] = sumValue<(0.5*(count-1))?(0.5*(count-1)-sumValue):0; } } } void depressR(float* srcR, _point* dstR, int* numDst, _size imgSize, _size depressWin) { *numDst = 0; for(int i=0; i<imgSize.height; i++) { for(int j=0; j<imgSize.width; j++) { float flagValue = srcR[i*imgSize.width+j]; int numPoint = 0, numPass = 0; for(int m=i-depressWin.height; m<=i+depressWin.height; m++) { for(int n=j-depressWin.width; n<=j+depressWin.width; n++) { if(m>=0 && m<imgSize.height && n>=0 && n<imgSize.width) { float compareValue = srcR[m*imgSize.width+n]; if(flagValue > compareValue) numPass ++; numPoint ++; } } } if(numPoint == numPass+1) { _point corner; corner.x = j; corner.y = i; dstR[(*numDst)++] = corner; } } } }