У меня есть набор точек, которые представляют собой небольшую дугу круга.
Текущий код помещает окружность в эти точки, используя линейные наименьшие квадраты:
void fit_circle(const std::vector<cv::Point2d> &pnts,cv::Point2d ¢re, double &radius)
{
int cols = 3;
cv::Mat X( static_cast<int>(pnts.size()), cols, CV_64F );
cv::Mat Y( static_cast<int>(pnts.size()), 1, CV_64F );
cv::Mat C;
if (int(pnts.size()) >= 3 )
{
for (size_t i = 0; i < pnts.size(); i++)
{
X.at<double>(static_cast<int>(i),0) = 2 * pnts[i].x;
X.at<double>(static_cast<int>(i),1) = 2 * pnts[i].y;
X.at<double>(static_cast<int>(i),2) = -1.0;
Y.at<double>(static_cast<int>(i),0) = (pnts[i].x * pnts[i].x + pnts[i].y * pnts[i].y);
}
cv::solve(X,Y,C,cv::DECOMP_SVD);
}
std::vector<double> coefs;
C.col(0).copyTo(coefs);
centre.x = coefs[0];
centre.y = coefs[1];
radius = sqrt ( coefs[0] * coefs[0] + coefs[1] * coefs[1] - coefs[2] );
}
Тем не мение, У меня есть ограничение, что радиус должен быть в пределах определенного диапазона,
то есть minRadius <= radius <= maxRadius
,
Таким образом, следует выбирать наилучший подходящий круг с радиусом в пределах этого диапазона вместо общего наилучшего соответствия.
Как я могу изменить код, чтобы добавить это ограничение?
Надеюсь, я правильно понял вашу проблему. Первая идея, вероятно, будет: использовать алгоритм подгонки с ограничениями. Я не очень люблю их, так как они очень трудоемки. Мой подход заключается в том, чтобы ограничить радиус, заменив его функцией типа r=r_min+(r_max-r_min)*0.5*(1+tanh(x))
и примерка x
, Так x
может варьироваться во всем диапазоне действительных чисел, давая вам радиусы в заданных пределах. Все остается простой нелинейной подборкой непрерывных функций.
В качестве примера на Python это выглядит так:
import matplotlib
matplotlib.use('Qt4Agg')
from matplotlib import pyplot as plt
from random import random
from scipy import optimize
import numpy as np
def boxmuller(x0,sigma):
u1=random()
u2=random()
ll=np.sqrt(-2*np.log(u1))
z0=ll*np.cos(2*np.pi*u2)
z1=ll*np.cos(2*np.pi*u2)
return sigma*z0+x0, sigma*z1+x0
def random_arcpoint(x0,y0, r,f1,f2,s):
arc=f1+(f2-f1)*random()
rr,_=boxmuller(r,s)
return [x0+rr*np.cos(arc),y0+rr*np.sin(arc)]def residuals(parameters,dataPoint):
xc,yc,Ri = parameters
distance = [np.sqrt( (x-xc)**2 + (y-yc)**2 ) for x,y in dataPoint]
res = [(Ri-dist)**2 for dist in distance]
return resdef residualsLim(parameters,dataPoint,rMin,rMax):
xc,yc,rP = parameters
Ri=rMin+(rMax-rMin)*0.5*(1+np.tanh(rP))
distance = [np.sqrt( (x-xc)**2 + (y-yc)**2 ) for x,y in dataPoint]
res = [(Ri-dist)**2 for dist in distance]
return resdef f0(phi,x0,y0,r):
return [x0+r*np.cos(phi),y0+r*np.sin(phi)]def f_lim(phi,rMin,rMax,x0,y0,x):
rr=(rMin+(rMax-rMin)*0.5*(1+np.tanh(x)))
return [x0+rr*np.cos(phi), y0+rr*np.sin(phi)]data=np.array([random_arcpoint(2.1,-1.2,11,1.0,3.144,.61) for s in range(200)])
estimate = [0, 0, 10]
bestFitValues_01, ier_01 = optimize.leastsq(residuals, estimate,args=(data))
print bestFitValues_01
bestFitValues_02, ier_02 = optimize.leastsq(residualsLim, estimate,args=(data,2,15))
print bestFitValues_02, 2+(15-2)*0.5*(1+np.tanh(bestFitValues_02[-1]))
bestFitValues_03, ier_03 = optimize.leastsq(residualsLim, estimate,args=(data,2,8))
print bestFitValues_03, 2+(8-2)*0.5*(1+np.tanh(bestFitValues_03[-1]))
bestFitValues_04, ier_04 = optimize.leastsq(residualsLim, estimate,args=(data,14,24))
print bestFitValues_04, 14+(24-14)*0.5*(1+np.tanh(bestFitValues_04[-1]))
pList=np.linspace(0,2*np.pi,35)
rList_01=np.array([f0(p,*bestFitValues_01) for p in pList])rList_02=np.array([f_lim(p,2,15,*bestFitValues_02) for p in pList])
rList_03=np.array([f_lim(p,2,8,*bestFitValues_03) for p in pList])
rList_04=np.array([f_lim(p,14,24,*bestFitValues_04) for p in pList])fig = plt.figure()
ax = fig.add_subplot(111)
ax.scatter(data[:,0],data[:,1])
ax.plot(rList_01[:,0],rList_01[:,1])
ax.plot(rList_02[:,0],rList_02[:,1],linestyle="--")
ax.plot(rList_03[:,0],rList_03[:,1],linestyle="--")
ax.plot(rList_04[:,0],rList_04[:,1],linestyle="--")plt.show()>> [ 2.05070788 -1.12399476 11.02276442]
>> [ 2.05071109 -1.12399791 0.40958281] 11.0227695567
>> [ 3.32479577e-01 2.14732017e+00 6.60281574e+02] 8.0
>> [ 3.64934819 -4.14597378 -679.24155201] 14.0
Если радиус находится в пределах интервала, результат совпадает с простым подгонкой. Иначе x
становится очень большим положительным или отрицательным числом, в основном это означает, что r
это один из ваших ограничений. Приятно то, что это непрерывно.
Результаты для данного кода выглядят как
Синие точки: случайные данные, синие диаграммы: простая подгонка, желтые диаграммы: подгонка с r
внутри границ, зеленый график: соответствует r
больше, чем верхняя граница, красный график: соответствует r
меньше нижней границы.
Почему бы не использовать HoughCircles()
? Это позволяет вам указать оба параметра. Пример взят из связанных документов, изменен для добавления параметров минимального и максимального радиуса:
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
#include <math.h>
using namespace cv;
int main(int argc, char** argv)
{
Mat img, gray;
img = imread('ring.png')
cvtColor(img, gray, COLOR_BGR2GRAY);
// smooth it, otherwise a lot of false circles may be detected
medianBlur(gray, gray, 7);
vector<Vec3f> circles;
int minRadius = 90
int maxRadius = 100
HoughCircles(gray, circles, HOUGH_GRADIENT,
1, 100, 20, 20, minRadius, maxRadius);
for( size_t i = 0; i < circles.size(); i++ )
{
Point center(cvRound(circles[i][0]), cvRound(circles[i][1]));
int radius = cvRound(circles[i][2]);
// draw the circle center
circle( img, center, 3, Scalar(0,255,0), -1, 8, 0 );
// draw the circle outline
circle( img, center, radius, Scalar(0,0,255), 3, 8, 0 );
}
namedWindow( "circles", 1 );
imshow( "circles", img );
return 0;
}
Пример выполнения (обратите внимание, я сделал это с Python, но с теми же параметрами):
Изображение украдено из аспирант в КМУ.
Используйте уравнение координат из
https://de.wikipedia.org/wiki/Kreis
G: a*x + b*y + c + y^2 + x^2 = 0
with
a: -2*xm
b: -2*ym
c: ym^2+xm^2-r^2
(Это точное уравнение не упоминается в английской Википедии, но упоминается базовая форма.)
Чтобы получить линейную регрессию, дифференцируйте G ^ 2 для a / b / c
Тогда вы получите в виде матрицы
A B C R R
G1: +2*a*x^2 +2*b*x*y +2*c*x +2*x^3 +2*x*y^2 = 0
G2: +2*a*x*y +2*b*y^2 +2*c*y +2*y^3 +2*x^2*y = 0
G3: +2*a*x +2*b*y +2*c +2*y^2 +2*x^2 = 0
Это производные уравнения вы можете решить с вашими точками для a / b / c. Из a / b / c вы можете заменить, чтобы получить xm / ym / r. Чтобы отклонить определенный радиус, просто проверьте его пределы.
Пример:
#include <opencv2/opencv.hpp>
#define _USE_MATH_DEFINES
#include <math.h>
#include <vector>
#include <random>
void fit_circle(const std::vector<cv::Point2d> &pnts, cv::Point2d ¢re,
double &radius)
{
/*
A B C R R
G1: +2*a*x^2 +2*b*x*y +2*c*x +2*x^3 +2*x*y^2 = 0
G2: +2*a*x*y +2*b*y^2 +2*c*y +2*y^3 +2*x^2*y = 0
G3: +2*a*x +2*b*y +2*c +2*y^2 +2*x^2 = 0
*/
static const int rows = 3;
static const int cols = 3;
cv::Mat LHS(rows, cols, CV_64F, 0.0);
cv::Mat RHS(rows, 1, CV_64F, 0.0);
cv::Mat solution(rows, 1, CV_64F, 0.0);
if (pnts.size() < 3)
{
throw std::runtime_error("To less points");
}
for (int i = 0; i < static_cast<int>(pnts.size()); i++)
{
double x1 = pnts[i].x;
double x2 = std::pow(pnts[i].x, 2);
double x3 = std::pow(pnts[i].x, 3);
double y1 = pnts[i].y;
double y2 = std::pow(pnts[i].y, 2);
double y3 = std::pow(pnts[i].y, 3);
// col 0 = A / col 1 = B / col 2 = C
// Row 0 = G1
LHS.at<double>(0, 0) += 2 * x2;
LHS.at<double>(0, 1) += 2 * x1 * y1;
LHS.at<double>(0, 2) += 2 * x1;
RHS.at<double>(0, 0) -= 2 * x3 + 2 * x1 * y2;
// Row 1 = G2
LHS.at<double>(1, 0) += 2 * x1 * y1;
LHS.at<double>(1, 1) += 2 * y2;
LHS.at<double>(1, 2) += 2 * y1;
RHS.at<double>(1, 0) -= 2 * y3 + 2 * x2 * y1;
// Row 2 = G3
LHS.at<double>(2, 0) += 2 * x1;
LHS.at<double>(2, 1) += 2 * y1;
LHS.at<double>(2, 2) += 2;
RHS.at<double>(2, 0) -= 2 * y2 + 2 * x2;
}
cv::solve(LHS, RHS, solution);
std::vector<double> abc{solution.at<double>(0, 0),
solution.at<double>(1, 0),
solution.at<double>(2, 0)};
centre.x = abc[0] / -2.0;
centre.y = abc[1] / -2.0;
radius = std::sqrt(
std::abs(std::pow(centre.x, 2) + std::pow(centre.y, 2) - abc[2]));
}
int main(int argc, const char *argv[])
{
try
{
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_real_distribution<> dis(-0.5, 0.5);
// Generate reandom points
double radius_in = 25;
double xm_in = 10;
double ym_in = 20;
std::vector<cv::Point2d> pnts;
{
for (double ang = 0; ang <= 90; ang += 10)
{
cv::Point2d p(
cos(ang / 180.0 * M_PI) * radius_in + xm_in + dis(gen),
sin(ang / 180.0 * M_PI) * radius_in + ym_in + dis(gen));
pnts.push_back(p);
}
}
cv::Point2d c_out;
double radius_out = 0;
fit_circle(pnts, c_out, radius_out);
double dxm = c_out.x - xm_in;
double dym = c_out.y - ym_in;
double dradius = radius_out - radius_in;
std::cout << "Deltas: " << dxm << " " << dym << " " << dradius
<< std::endl;
return EXIT_SUCCESS;
}
catch (const std::exception &ex)
{
std::cerr << ex.what() << std::endl;
}
return EXIT_FAILURE;
}
Это выводы
Дельты: 0,180365 0,190016 -0,231563
РЕДАКТИРОВАТЬ: Добавить источник для создания статистики и статистики об алгоритме
#include <opencv2/opencv.hpp>
#define _USE_MATH_DEFINES
#include <math.h>
#include <vector>
#include <random>
void fit_circle(const std::vector<cv::Point2d> &pnts, cv::Point2d ¢re,
double &radius)
{
/*
A B C R R
G1: +2*a*x^2 +2*b*x*y +2*c*x +2*x^3 +2*x*y^2 = 0
G2: +2*a*x*y +2*b*y^2 +2*c*y +2*y^3 +2*x^2*y = 0
G3: +2*a*x +2*b*y +2*c +2*y^2 +2*x^2 = 0
*/
static const int rows = 3;
static const int cols = 3;
cv::Mat LHS(rows, cols, CV_64F, 0.0);
cv::Mat RHS(rows, 1, CV_64F, 0.0);
cv::Mat solution(rows, 1, CV_64F, 0.0);
if (pnts.size() < 3)
{
throw std::runtime_error("To less points");
}
for (int i = 0; i < static_cast<int>(pnts.size()); i++)
{
double x1 = pnts[i].x;
double x2 = std::pow(pnts[i].x, 2);
double x3 = std::pow(pnts[i].x, 3);
double y1 = pnts[i].y;
double y2 = std::pow(pnts[i].y, 2);
double y3 = std::pow(pnts[i].y, 3);
// col 0 = A / col 1 = B / col 2 = C
// Row 0 = G1
LHS.at<double>(0, 0) += 2 * x2;
LHS.at<double>(0, 1) += 2 * x1 * y1;
LHS.at<double>(0, 2) += 2 * x1;
RHS.at<double>(0, 0) -= 2 * x3 + 2 * x1 * y2;
// Row 1 = G2
LHS.at<double>(1, 0) += 2 * x1 * y1;
LHS.at<double>(1, 1) += 2 * y2;
LHS.at<double>(1, 2) += 2 * y1;
RHS.at<double>(1, 0) -= 2 * y3 + 2 * x2 * y1;
// Row 2 = G3
LHS.at<double>(2, 0) += 2 * x1;
LHS.at<double>(2, 1) += 2 * y1;
LHS.at<double>(2, 2) += 2;
RHS.at<double>(2, 0) -= 2 * y2 + 2 * x2;
}
cv::solve(LHS, RHS, solution);
std::vector<double> abc{solution.at<double>(0, 0),
solution.at<double>(1, 0),
solution.at<double>(2, 0)};
centre.x = abc[0] / -2.0;
centre.y = abc[1] / -2.0;
radius = std::sqrt(
std::abs(std::pow(centre.x, 2) + std::pow(centre.y, 2) - abc[2]));
}
int main(int argc, const char *argv[])
{
int count = 100;
if (argc == 2)
{
count = std::atoi(argv[1]);
}
try
{
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_real_distribution<> jitter(-0.5, 0.5);
std::uniform_real_distribution<> pos(-100, 100);
std::uniform_real_distribution<> w_start(0, 360);
std::uniform_real_distribution<> w_length(10, 180);
std::uniform_real_distribution<> rad(10, 180);
std::cout << "start\tlen\tdelta xm\tdelta ym\tdelta radius" << std::endl;
for (int i = 0; i < count; ++i)
{
// Generate reandom points
double radius_in = rad(gen);
double xm_in = pos(gen);
double ym_in = pos(gen);
double start = w_start(gen);
double len = w_length(gen);
std::vector<cv::Point2d> pnts;
{
for (double ang = start; ang <= start + len; ang += 1)
{
cv::Point2d p(
cos(ang / 180.0 * M_PI) * radius_in + xm_in + jitter(gen),
sin(ang / 180.0 * M_PI) * radius_in + ym_in + jitter(gen));
pnts.push_back(p);
}
}
cv::Point2d c_out;
double radius_out = 0;
fit_circle(pnts, c_out, radius_out);
double dxm = c_out.x - xm_in;
double dym = c_out.y - ym_in;
double dradius = radius_out - radius_in;
std::cout << start << "\t" << len << "\t" << dxm << "\t" << dym << "\t" << dradius
<< std::endl;
}
return EXIT_SUCCESS;
}
catch (const std::exception &ex)
{
std::cerr << ex.what() << std::endl;
}
return EXIT_FAILURE;
}
Участок сформирован:
set multiplot layout 3,1 rowsfirst
set yrange [-50:50]
set grid
plot 'stat.txt' using 2:3 title 'segment-length vs delta x' with points ps 0.1
set yrange [-50:50]
set grid
plot 'stat.txt' using 2:4 title 'segment-length vs delta y' with points ps 0.1
set yrange [-50:50]
set grid
plot 'stat.txt' using 2:5 title 'segment-length vs delta radius' with points ps 0.1