Skip to content

Commit adb210f

Browse files
committed
handle rotated bananas
so far the center line had only been calculated correctly if the banana was lying more-or-less horizontally. however, thanks to the PCA analysis we know the rotation angle and center of the banana. we can use this to rotate the contour first so that the banana is considered horizontal (length-wise), which allows us to correctly fit a polynomial through it to approximate the center line. for rendering we then have to transform back the line to put it over the banana again.
1 parent 78b761a commit adb210f

File tree

2 files changed

+42
-15
lines changed

2 files changed

+42
-15
lines changed

include/banana-lib/lib.hpp

+18-2
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ namespace banana {
5555
Contour contour;
5656
/**
5757
* The coefficients a0, a1 and a2 of the two-dimensional polynomial describing the center line of the banana.
58+
* Important: note that this is given along the primary axis of the banana and not in relation to the x-axis of the image.
5859
*
5960
* The center line is defined approximately by the method $y = a0 + a1 * x + a2 * x^2$.
6061
*/
@@ -150,13 +151,28 @@ namespace banana {
150151
auto FindBananaContours(cv::Mat const& image) const -> Contours;
151152

152153
/**
153-
* Calculate the coefficients of the two-dimensional polynomial describing the center line of the banana.
154+
* Calculate the coefficients of the two-dimensional polynomial describing the center line.
155+
* Important: note that this is given along the primary axis of the banana and not in relation to the x-axis of the image.
154156
*
155157
* @param banana_contour the contour of the banana to be analysed
158+
* @param pca_result the result from the PCA analysis
156159
* @return the coefficients of the two-dimensional polynomial describing the center line of the banana.
157160
*/
158161
[[nodiscard]]
159-
auto GetBananaCenterLineCoefficients(Contour const& banana_contour) const -> std::expected<std::tuple<double, double, double>, AnalysisError>;
162+
auto GetBananaCenterLineCoefficients(Contour const& banana_contour, PCAResult const& pca_result) const -> std::expected<std::tuple<double, double, double>, AnalysisError>;
163+
164+
/**
165+
* Rotate a contour by the defined angle around the specified point.
166+
*
167+
* @param contour the contour to be rotated
168+
* @param center the center around which the contour is to be rotated
169+
* @param angle the angle in radians by which it should be rotated
170+
* @return the rotated contour
171+
* @see cv::getRotationMatrix2D
172+
* @see cv::transform
173+
*/
174+
[[nodiscard]]
175+
auto RotateContour(Contour const& contour, cv::Point const& center, double const angle) const -> Contour;
160176

161177
/**
162178
* Calculate the PCA of the provided contour. This yields information about the center and rotation of the shape.

src/lib.cpp

+24-13
Original file line numberDiff line numberDiff line change
@@ -129,20 +129,22 @@ namespace banana {
129129
return contours;
130130
}
131131

132-
auto Analyzer::GetBananaCenterLineCoefficients(Contour const& banana_contour) const -> std::expected<std::tuple<double, double, double>, AnalysisError> {
132+
auto Analyzer::GetBananaCenterLineCoefficients(Contour const& banana_contour, PCAResult const& pca_result) const -> std::expected<std::tuple<double, double, double>, AnalysisError> {
133+
// rotate the contour so that it's horizontal
134+
auto const rotated_contour = this->RotateContour(banana_contour, pca_result.center, pca_result.angle);
135+
133136
auto const to_std_pair_fn = [](auto const& p) -> std::pair<double, double> { return {p.x, p.y}; };
134-
auto const coeffs = polyfit::Fit2DPolynomial(banana_contour | std::views::transform(to_std_pair_fn));
135-
#ifdef SHOW_DEBUG_INFO
136-
if (coeffs) {
137-
std::cout << std::format("y = {} + {} * x + {} * x^2", std::get<0>(*coeffs), std::get<1>(*coeffs),
138-
std::get<2>(*coeffs)) << std::endl;
139-
} else {
140-
std::cerr << "couldn't find a solution!" << std::endl;
141-
}
142-
#endif
137+
auto const coeffs = polyfit::Fit2DPolynomial(rotated_contour | std::views::transform(to_std_pair_fn));
143138
return coeffs.transform_error([](auto const& _) -> auto {return AnalysisError::kPolynomialCalcFailure;});
144139
}
145140

141+
auto Analyzer::RotateContour(Contour const& contour, cv::Point const& center, double const angle) const -> Contour {
142+
auto const rotation_matrix = cv::getRotationMatrix2D(center, angle * 180 / std::numbers::pi, 1);
143+
Contour rotated_contour{contour.size()};
144+
cv::transform(contour, rotated_contour, rotation_matrix);
145+
return rotated_contour;
146+
}
147+
146148
auto Analyzer::GetPCA(const Contour &banana_contour) const -> Analyzer::PCAResult {
147149
// implementation adapted from https://docs.opencv.org/4.9.0/d1/dee/tutorial_introduction_to_pca.html
148150

@@ -179,7 +181,7 @@ namespace banana {
179181

180182
auto Analyzer::AnalyzeBanana(cv::Mat const& image, Contour const& banana_contour) const -> std::expected<AnalysisResult, AnalysisError> {
181183
auto const pca = this->GetPCA(banana_contour);
182-
auto const coeffs = this->GetBananaCenterLineCoefficients(banana_contour);
184+
auto const coeffs = this->GetBananaCenterLineCoefficients(banana_contour, pca);
183185
if (!coeffs) {
184186
return std::unexpected{coeffs.error()};
185187
}
@@ -193,9 +195,15 @@ namespace banana {
193195
}
194196

195197
void Analyzer::PlotCenterLine(cv::Mat& draw_target, AnalysisResult const& result) const {
198+
// note that the coefficients for the center line are given in relation to the bananas main axis.
199+
// accordingly we have to rotate the resulting line to plot it over the banana in the image.
200+
201+
// rotate the contour so that it's horizontal (needed to calculate the x-axis points for plotting in the coordinate system of the banana).
202+
auto const rotated_contour = this->RotateContour(result.contour, result.estimated_center, result.rotation_angle);
203+
196204
auto const& [coeff_0, coeff_1, coeff_2] = result.center_line_coefficients;
197205

198-
auto const minmax_x = std::ranges::minmax(result.contour | std::views::transform(&cv::Point::x));
206+
auto const minmax_x = std::ranges::minmax(rotated_contour | std::views::transform(&cv::Point::x));
199207

200208
/// Calculate a Point2d for the [x,y] coords based on the provided polynomial and x-values.
201209
auto const calc_xy = [&coeff_0, &coeff_1, &coeff_2](auto const&& x) -> cv::Point2d {
@@ -210,7 +218,10 @@ namespace banana {
210218
auto const center_line_points = std::views::iota(start, end) | std::views::transform(calc_xy);
211219
auto const center_line_points2i = center_line_points | std::views::transform(to_point2i) | std::ranges::to<std::vector>();
212220

213-
cv::polylines(draw_target, center_line_points2i, false, this->helper_annotation_color_, 10);
221+
// rotate the center line back so that it fits on the image
222+
auto const rotated_center_line = this->RotateContour(center_line_points2i, result.estimated_center, -result.rotation_angle);
223+
224+
cv::polylines(draw_target, rotated_center_line, false, this->helper_annotation_color_, 10);
214225
}
215226

216227
void Analyzer::PlotPCAResult(cv::Mat& draw_target, AnalysisResult const& result) const {

0 commit comments

Comments
 (0)