一、理论
在已知像素边缘点后,可以通过分析边缘法线方向的像素,来插值出边缘亚像素点。
该方法假设边缘附近垂直于边缘方向上的灰度分布是严格线性的,沿着边缘方向找到灰度值阶跃最大位置就是边缘位置。在这里插入代码片
二、代码逻辑
给定一个像素级边缘点edge_point 以及该点的梯度方向gradient_dir(以度为单位,指向灰度增加的方向):
- 沿法线方向(即梯度方向)的正反方向各取距离 1 像素的亚像素点。
- 用双线性插值获取这两点的灰度值。
- 比较两点灰度,确定暗侧和亮侧。
- 将当前边缘点的灰度值视为边缘上的灰度,在暗、亮两点间进行线性插值,得到比例因子 𝑡。
- 由 𝑡 计算出亚像素坐标。
因为法线方向存在角度,获取边缘点两侧一个像素单位点的灰度值计算方法如下
cpp
/**
* 双线性插值函数 - 获取亚像素位置的灰度值
* @param img 输入图像(单通道)
* @param x, y 亚像素坐标
* @return 插值后的灰度值
*/
float bilinearInterpolate(const cv::Mat& img, float x, float y) {
// 边界检查
if (x < 0 || x >= img.cols - 1 || y < 0 || y >= img.rows - 1) {
// 返回最近的有效像素值
int ix = cv::borderInterpolate((int)round(x), img.cols, cv::BORDER_REFLECT);
int iy = cv::borderInterpolate((int)round(y), img.rows, cv::BORDER_REFLECT);
return img.at<uchar>(iy, ix);
}
int x0 = (int)floor(x);
int y0 = (int)floor(y);
int x1 = x0 + 1;
int y1 = y0 + 1;
// 确保不越界
x0 = max(0, min(img.cols - 1, x0));
x1 = max(0, min(img.cols - 1, x1));
y0 = max(0, min(img.rows - 1, y0));
y1 = max(0, min(img.rows - 1, y1));
float dx = x - x0;
float dy = y - y0;
// 获取四个像素值
float I00 = img.at<uchar>(y0, x0);
float I10 = img.at<uchar>(y0, x1);
float I01 = img.at<uchar>(y1, x0);
float I11 = img.at<uchar>(y1, x1);
// 双线性插值公式
float intensity = (1.0f - dx) * (1.0f - dy) * I00 +
dx * (1.0f - dy) * I10 +
(1.0f - dx) * dy * I01 +
dx * dy * I11;
return intensity;
}
边缘法线方向可通过sobel算子计算得出
cpp
Mat input_img = imread("circle.bmp", 0);
// Step 3: 计算图像梯度(用于边缘方向)
cv::Mat gx, gy;
cv::Sobel(input_img, gx, CV_32F, 1, 0, 5);
cv::Sobel(input_img, gy, CV_32F, 0, 1, 5);
cv::Mat magnitude, angle;
cv::cartToPolar(gx, gy, magnitude, angle, true); // 角度单位为度
获取源码请私信,可指导配置技术交流