|
#include <opencv2/opencv.hpp>
#include <vector>
#include <cmath>
#include <iostream>
using namespace cv;
using namespace std;
// === 创建方向一致的 DoG 卷积模板 ===
vector<float> createDoGKernel(int size, float sigma) {
vector<float> kernel(size);
int half = size / 2;
float norm = 0.0;
for (int i = -half; i <= half; ++i) {
float x = static_cast<float>(i);
float val = -x * exp(-x * x / (2 * sigma * sigma));
kernel[i + half] = val;
norm += fabs(val);
}
// 归一化(可选)
for (float& v : kernel)
v /= norm;
return kernel;
}
// === 主检测函数 ===
void detectCircularEdgeSubpixel(
const Mat& gray,
Point2f center,
float radius,
float bandWidth,
float sampleStep,
const vector<float>& kernel,
float responseThresh,
vector<Point2f>& edgePoints,
int numAngles = 100
) {
edgePoints.clear();
int kernelSize = kernel.size();
float angleStep = 360.0f / numAngles;
for (int a = 0; a < numAngles; ++a) {
double theta = a * CV_PI / 180.0;
// 采样灰度值
vector<uchar> profile;
vector<Point2f> samplePoints;
for (double rOffset = -bandWidth; rOffset <= bandWidth; rOffset += sampleStep) {
double r = radius + rOffset;
double x = center.x + r * cos(theta);
double y = center.y + r * sin(theta);
if (x >= 0 && x < gray.cols && y >= 0 && y < gray.rows) {
profile.push_back(gray.at<uchar>(round(y), round(x)));
samplePoints.push_back(Point2f(x, y));
} else {
profile.push_back(0);
samplePoints.push_back(Point2f(x, y));
}
}
// 卷积并找最大响应
double maxResp = 0;
int bestIdx = -1;
for (int j = 0; j <= profile.size() - kernelSize; ++j) {
double resp = 0;
for (int k = 0; k < kernelSize; ++k) {
resp += profile[j + k] * kernel[k];
}
if (resp > maxResp && resp > responseThresh) {
maxResp = resp;
bestIdx = j;
}
}
// 亚像素拟合(仅在有效位置)
if (bestIdx > 0 && bestIdx < profile.size() - kernelSize - 1) {
double Rm1 = 0, R0 = 0, Rp1 = 0;
for (int k = 0; k < kernelSize; ++k) {
Rm1 += profile[bestIdx - 1 + k] * kernel[k];
R0 += profile[bestIdx + k] * kernel[k];
Rp1 += profile[bestIdx + 1 + k] * kernel[k];
}
double delta = 0.0;
double denom = Rm1 - 2 * R0 + Rp1;
if (fabs(denom) > 1e-5) {
delta = 0.5 * (Rm1 - Rp1) / denom;
}
double rFinal = radius + ((bestIdx + delta) * sampleStep) - bandWidth;
double ex = center.x + rFinal * cos(theta);
double ey = center.y + rFinal * sin(theta);
edgePoints.push_back(Point2f(static_cast<float>(ex), static_cast<float>(ey)));
}
}
}
int main() {
// === 加载图像 ===
Mat gray = imread("circle.png", IMREAD_GRAYSCALE);
if (gray.empty()) {
cerr << "图像加载失败!" << endl;
return -1;
}
// === 初始化参数 ===
Point2f approxCenter(150.0f, 150.0f); // 初始估计中心
float approxRadius = 50.0f;
float bandWidth = 5.0f;
float sampleStep = 0.5f;
int kernelSize = 5;
float sigma = 1.0f;
float responseThreshold = 50.0f;
// === 构造 DoG 卷积模板 ===
vector<float> patternKernel = createDoGKernel(kernelSize, sigma);
// === 边缘检测 ===
vector<Point2f> edgePoints;
detectCircularEdgeSubpixel(gray, approxCenter, approxRadius, bandWidth,
sampleStep, patternKernel, responseThreshold, edgePoints);
// === 拟合圆 ===
Point2f fittedCenter;
float fittedRadius = 0.0f;
if (!edgePoints.empty()) {
minEnclosingCircle(edgePoints, fittedCenter, fittedRadius);
cout << "拟合圆心: " << fittedCenter << ", 半径: " << fittedRadius << endl;
// 可视化
Mat display;
cvtColor(gray, display, COLOR_GRAY2BGR);
for (auto& pt : edgePoints) {
circle(display, pt, 1, Scalar(0, 0, 255), -1);
}
circle(display, fittedCenter, static_cast<int>(fittedRadius), Scalar(0, 255, 0), 2);
imshow("Fitted Circle", display);
waitKey(0);
} else {
cout << "未检测到边缘点。" << endl;
}
return 0;
}
|
|