@@ -50,6 +50,7 @@ namespace banana {
50
50
o << " Mean curvature = " << std::format (" {:.2f}" , banana.mean_curvature / 100 ) << " 1/cm"
51
51
<< " (corresponds to a circle with radius = " << std::format (" {:.2f}" , 1 /banana.mean_curvature * 100 ) << " cm)" << std::endl;
52
52
o << " Length along center line = " << std::format (" {:.2f}" , banana.length * 100 ) << " cm" << std::endl;
53
+ o << " ripeness= " << std::format (" {:.0f}" , banana.ripeness * 100 ) << " %" << std::endl;
53
54
o << std::endl;
54
55
}
55
56
@@ -94,16 +95,12 @@ namespace banana {
94
95
});
95
96
}
96
97
97
- auto Analyzer::ColorFilter (cv::Mat const & image) const -> cv::Mat {
98
+ auto Analyzer::ColorFilter (cv::Mat const & image, cv::Scalar low, cv::Scalar up ) const -> cv::Mat {
98
99
cv::Mat hsvImage;
99
100
cv::cvtColor (image, hsvImage, cv::COLOR_BGR2HSV);
100
101
101
- // Define the range for colors in the HSV color space
102
- auto const lowerThreshold = cv::Scalar (0 , 41 , 0 );
103
- auto const upperThreshold = cv::Scalar (177 , 255 , 255 );
104
-
105
102
cv::Mat mask;
106
- cv::inRange (hsvImage, lowerThreshold, upperThreshold , mask);
103
+ cv::inRange (hsvImage, low, up , mask);
107
104
108
105
return mask;
109
106
}
@@ -113,7 +110,7 @@ namespace banana {
113
110
}
114
111
115
112
auto Analyzer::FindBananaContours (cv::Mat const & image) const -> Contours {
116
- auto filtered_image = ColorFilter (image);
113
+ auto filtered_image = ColorFilter (image, settings_. filter_lower_threshold_color , settings_. filter_upper_threshold_color );
117
114
SHOW_DEBUG_IMAGE (filtered_image, " color filtered image" );
118
115
119
116
// Removing noise
@@ -122,7 +119,7 @@ namespace banana {
122
119
SHOW_DEBUG_IMAGE (filtered_image, " morph" );
123
120
124
121
// Smooth the image
125
- cv::medianBlur (filtered_image, filtered_image, 37 ); // TODO: test again with 41
122
+ cv::medianBlur (filtered_image, filtered_image, 37 );
126
123
SHOW_DEBUG_IMAGE (filtered_image, " blur" );
127
124
128
125
Contours contours;
@@ -206,7 +203,6 @@ namespace banana {
206
203
207
204
auto const x = center_line.points_in_banana_coordsys
208
205
| std::views::transform (&cv::Point2d::x);
209
- // | std::views::transform(px_to_m);
210
206
211
207
auto const calc_first_deriv = [coeff_1, coeff_2](auto const & x) -> auto {
212
208
return 2 * coeff_2 * x + coeff_1;
@@ -240,6 +236,36 @@ namespace banana {
240
236
return length_in_px / this ->settings_ .pixels_per_meter ;
241
237
}
242
238
239
+ auto Analyzer::GetMaskedImage (const cv::Mat& image, const Contour& contour) const -> cv::Mat {
240
+ auto mask = cv::Mat{image.size (), CV_8UC3, cv::Scalar{255 ,255 ,255 }};
241
+ cv::drawContours (mask, std::vector{{contour}}, -1 , {0 ,0 ,0 }, cv::FILLED);
242
+ SHOW_DEBUG_IMAGE (mask, " mask" );
243
+ cv::Mat masked;
244
+ cv::bitwise_or (image, mask, masked);
245
+ SHOW_DEBUG_IMAGE (masked, " filtered image (masked area only)" );
246
+ return masked;
247
+ }
248
+
249
+ auto Analyzer::IdentifyBananaRipeness (const cv::Mat& banana_image) const -> float {
250
+ // / mask for green, yellow and brown colors
251
+ auto const green_mask = ColorFilter (banana_image, settings_.green_lower_threshold_color , settings_.green_upper_threshold_color );
252
+ auto const yellow_mask = ColorFilter (banana_image, settings_.yellow_lower_threshold_color , settings_.yellow_upper_threshold_color );
253
+ auto const brown_mask = ColorFilter (banana_image, settings_.brown_lower_threshold_color , settings_.brown_upper_threshold_color );
254
+
255
+ // / count pixels in the three color spaces
256
+ auto const green_pixel_count = cv::countNonZero (green_mask);
257
+ auto const yellow_pixel_count = cv::countNonZero (yellow_mask);
258
+ auto const brown_pixel_count = cv::countNonZero (brown_mask);
259
+
260
+ auto const total_pixel_count = green_pixel_count + yellow_pixel_count + brown_pixel_count;
261
+ float green_share = static_cast <float >(green_pixel_count) / (static_cast <float >(total_pixel_count)+1e-3f );
262
+ float brown_share = static_cast <float >(brown_pixel_count) / (static_cast <float >(total_pixel_count)+1e-3f );
263
+
264
+ // assumption: if 100% is yellow we consider it ripe.
265
+ // the more brown there is the riper it is, the more green there is the more unripe it is
266
+ return 1 - green_share + brown_share;
267
+ }
268
+
243
269
auto Analyzer::AnalyzeBanana (cv::Mat const & image, Contour const & banana_contour) const -> std::expected<AnalysisResult, AnalysisError> {
244
270
auto const pca = this ->GetPCA (banana_contour);
245
271
@@ -256,13 +282,16 @@ namespace banana {
256
282
.points_in_banana_coordsys = this ->GetBananaCenterLine (rotated_contour, *coeffs),
257
283
};
258
284
285
+ auto const banana_only = this ->GetMaskedImage (image, banana_contour);
286
+
259
287
return AnalysisResult{
260
288
.contour = banana_contour,
261
289
.center_line = center_line,
262
290
.rotation_angle = pca.angle ,
263
291
.estimated_center = pca.center ,
264
292
.mean_curvature = this ->CalculateMeanCurvature (center_line),
265
293
.length = this ->CalculateBananaLength (center_line),
294
+ .ripeness = this ->IdentifyBananaRipeness (banana_only),
266
295
};
267
296
}
268
297
0 commit comments