在网上看到有人写的Zernike矩亚像素边缘检测,发现存在很大的问题,赌气自己写了一个版本,力求精简,没有做过多优化。模板采用(7,7),借用Opencv的数据结构。如有问题欢迎指出。
关于Zernike矩求亚像素的原理,可以百度,有很多资源,下面是本人参考的文献:
Zernike矩亚像素原理
下面直接上代码:
#include
#include
#include
using namespace cv;
using namespace std;
const double PI = 3.14159265358979323846;
const int g_N = 7;
const int h_N = 3;
Mat M00 = (Mat_<float>(7, 7) <<
0, 0.0287, 0.0686, 0.0807, 0.0686, 0.0287, 0,
0.0287, 0.0815, 0.0816, 0.0816, 0.0816, 0.0815, 0.0287,
0.0686, 0.0816, 0.0816, 0.0816, 0.0816, 0.0816, 0.0686,
0.0807, 0.0816, 0.0816, 0.0816, 0.0816, 0.0816, 0.0807,
0.0686, 0.0816, 0.0816, 0.0816, 0.0816, 0.0816, 0.0686,
0.0287, 0.0815, 0.0816, 0.0816, 0.0816, 0.0815, 0.0287,
0, 0.0287, 0.0686, 0.0807, 0.0686, 0.0287, 0);
Mat M11R = (Mat_<float>(7, 7) <<
0, -0.015, -0.019, 0, 0.019, 0.015, 0,
-0.0224, -0.0466, -0.0233, 0, 0.0233, 0.0466, 0.0224,
-0.0573, -0.0466, -0.0233, 0, 0.0233, 0.0466, 0.0573,
-0.069, -0.0466, -0.0233, 0, 0.0233, 0.0466, 0.069,
-0.0573, -0.0466, -0.0233, 0, 0.0233, 0.0466, 0.0573,
-0.0224, -0.0466, -0.0233, 0, 0.0233, 0.0466, 0.0224,
0, -0.015, -0.019, 0, 0.019, 0.015, 0);
Mat M11I = (Mat_<float>(7, 7) <<
0, -0.0224, -0.0573, -0.069, -0.0573, -0.0224, 0,
-0.015, -0.0466, -0.0466, -0.0466, -0.0466, -0.0466, -0.015,
-0.019, -0.0233, -0.0233, -0.0233, -0.0233, -0.0233, -0.019,
0, 0, 0, 0, 0, 0, 0,
0.019, 0.0233, 0.0233, 0.0233, 0.0233, 0.0233, 0.019,
0.015, 0.0466, 0.0466, 0.0466, 0.0466, 0.0466, 0.015,
0, 0.0224, 0.0573, 0.069, 0.0573, 0.0224, 0);
Mat M20 = (Mat_<float>(7, 7) <<
0, 0.0225, 0.0394, 0.0396, 0.0394, 0.0225, 0,
0.0225, 0.0271, -0.0128, -0.0261, -0.0128, 0.0271, 0.0225,
0.0394, -0.0128, -0.0528, -0.0661, -0.0528, -0.0128, 0.0394,
0.0396, -0.0261, -0.0661, -0.0794, -0.0661, -0.0261, 0.0396,
0.0394, -0.0128, -0.0528, -0.0661, -0.0528, -0.0128, 0.0394,
0.0225, 0.0271, -0.0128, -0.0261, -0.0128, 0.0271, 0.0225,
0, 0.0225, 0.0394, 0.0396, 0.0394, 0.0225, 0);
Mat M31R = (Mat_<float>(7, 7) <<
0, -0.0103, -0.0073, 0, 0.0073, 0.0103, 0,
-0.0153, -0.0018, 0.0162, 0, -0.0162, 0.0018, 0.0153,
-0.0223, 0.0324, 0.0333, 0, -0.0333, -0.0324, 0.0223,
-0.0190, 0.0438, 0.0390, 0, -0.0390, -0.0438, 0.0190,
-0.0223, 0.0324, 0.0333, 0, -0.0333, -0.0324, 0.0223,
-0.0153, -0.0018, 0.0162, 0, -0.0162, 0.0018, 0.0153,
0, -0.0103, -0.0073, 0, 0.0073, 0.0103, 0);
Mat M31I = (Mat_<float>(7, 7) <<
0, -0.0153, -0.0223, -0.019, -0.0223, -0.0153, 0,
-0.0103, -0.0018, 0.0324, 0.0438, 0.0324, -0.0018, -0.0103,
-0.0073, 0.0162, 0.0333, 0.039, 0.0333, 0.0162, -0.0073,
0, 0, 0, 0, 0, 0, 0,
0.0073, -0.0162, -0.0333, -0.039, -0.0333, -0.0162, 0.0073,
0.0103, 0.0018, -0.0324, -0.0438, -0.0324, 0.0018, 0.0103,
0, 0.0153, 0.0223, 0.0190, 0.0223, 0.0153, 0);
Mat M40 = (Mat_<float>(7, 7) <<
0, 0.013, 0.0056, -0.0018, 0.0056, 0.013, 0,
0.0130, -0.0186, -0.0323, -0.0239, -0.0323, -0.0186, 0.0130,
0.0056, -0.0323, 0.0125, 0.0406, 0.0125, -0.0323, 0.0056,
-0.0018, -0.0239, 0.0406, 0.0751, 0.0406, -0.0239, -0.0018,
0.0056, -0.0323, 0.0125, 0.0406, 0.0125, -0.0323, 0.0056,
0.0130, -0.0186, -0.0323, -0.0239, -0.0323, -0.0186, 0.0130,
0, 0.013, 0.0056, -0.0018, 0.0056, 0.013, 0);
int main()
{
cv::Mat gray_img, med_img, canny_img, border_img, border_edge;
string image_path = "lena.jpg";
gray_img = cv::imread(image_path, 0); //以灰度图形式读取
cv::medianBlur(gray_img, med_img, 5); //中值滤波,不是必须,只为消除部分椒盐噪声
cv::Canny(med_img, canny_img, 80, 120); //canny边缘检测,进行初定位,获取初始边缘
//由于图像用到的模板为7*7的,所以先对图像进行边缘扩展
copyMakeBorder(med_img, border_img, h_N, h_N, h_N, h_N, cv::BORDER_REPLICATE);
copyMakeBorder(canny_img, border_edge, h_N, h_N, h_N, h_N, cv::BORDER_REPLICATE);
float Z00 = 0, Z11_I = 0, Z11_R = 0, Z20 = 0, Z31_I = 0, Z31_R = 0, Z40 = 0; //不同模板的卷积值
cv::Mat roi_img;
float theta1, theta3,Zz11, Zz31, Zz20, Zz40, l, r, t;
float x, y;
vector<cv::Point2f> sub_pixel_edge;
//对初始边缘进行亚像素边缘求取
for (int i = h_N; i < border_img.rows - h_N; i++)
{
for (int j = h_N; j < border_img.cols - h_N; j++)
{
if (border_edge.at<uchar>(i, j) == 255)
{
//卷积
border_img(cv::Rect(j - h_N, i - h_N, g_N, g_N)).convertTo(roi_img, CV_32FC1);
Z00 = roi_img.dot(M00);
Z11_I = roi_img.dot(M11I);
Z11_R = roi_img.dot(M11R);
Z20 = roi_img.dot(M20);
/*Z31_I = roi_img.dot(M31I);
Z31_R = roi_img.dot(M31R);
Z40 = roi_img.dot(M40); */
theta1 = atan(Z11_I / Z11_R); //旋转角度计算
Zz11 = Z11_R * cos(theta1) + Z11_I * sin(theta1); //各阶Zernike矩旋转后结果
l = Z20 / Zz11; //边缘距离圆心的距离
r = 1.5 * Zz11 / sqrt(pow(1 - pow(l, 2), 3)); //阶跃幅度
t = (Z00 - r * PI / 2 + r * asin(l) + r * l * sqrt(1 - pow(l, 2))) / PI; //背景灰度值计算
//计算亚像素位置
x = j + g_N / 2.0 * l * cos(theta1) - h_N; //根据公式计算即可,因扩充了边缘需要减去
y = i + g_N / 2.0 * l * sin(theta1) - h_N;
cv::Point2f sub_pixel_point(x, y);
sub_pixel_edge.push_back(sub_pixel_point); //存储亚像素边缘位置
}
}
}
cv::Mat draw_image;
cv::cvtColor(canny_img, draw_image, cv::COLOR_GRAY2RGB);
//绘制亚像素边缘图
for (int i = 0; i < sub_pixel_edge.size(); i++)
{
circle(draw_image, sub_pixel_edge[i], 0, Scalar(255, 0, 255), 1, 0, 0);
}
imshow("sub pixel edge", draw_image);
cv::waitKey();
return 0;
}
原图如下:
Canny结果如下
亚像素结果如下:
图中白色为Canny的像素位置,红色为亚像素的位置。因为图像像素只能画出整像素位置,所以仅供显示,看一下效果。若要准确对比相对位置,可以把亚像素坐标存储起来,用matlab画出来进行对比。