Skip to content

Commit cbdb37d

Browse files
authored
Merge pull request #30 from ichiro-its/feature/add-lab-classification
[PI Sprint 25/26] [Feature] - Add LAB Color Classification
2 parents 24cd3a4 + d3ce9f5 commit cbdb37d

File tree

10 files changed

+151
-68
lines changed

10 files changed

+151
-68
lines changed

include/ninshiki_cpp/detector/color_detector.hpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ class ColorDetector : public Detector
5353

5454
cv::Mat classify(cv::Mat input);
5555
cv::Mat classify_gray(cv::Mat input);
56+
cv::Mat classify_lab(cv::Mat input);
5657

5758
int get_min_hue() {return min_hue;}
5859
int get_max_hue() {return max_hue;}
@@ -68,6 +69,20 @@ class ColorDetector : public Detector
6869
void set_min_value(int value) {min_value = keisan::clamp(value, 0, 100);}
6970
void set_max_value(int value) {max_value = keisan::clamp(value, 0, 100);}
7071

72+
int get_min_lightness() {return min_hue;}
73+
int get_max_lightness() {return max_lightness;}
74+
int get_min_a() {return min_a;}
75+
int get_max_a() {return max_a;}
76+
int get_min_b() {return min_b;}
77+
int get_max_b() {return max_b;}
78+
79+
void set_min_lightness(int value) {min_lightness = keisan::clamp(value, 0, 255);}
80+
void set_max_lightness(int value) {max_lightness = keisan::clamp(value, 0, 255);}
81+
void set_min_a(int value) {min_a = keisan::clamp(value, -128, 128);}
82+
void set_max_a(int value) {max_a = keisan::clamp(value, -128, 128);}
83+
void set_min_b(int value) {min_b = keisan::clamp(value, -128, 128);}
84+
void set_max_b(int value) {max_b = keisan::clamp(value, -128, 128);}
85+
7186
// Function for Contours
7287
void find(cv::Mat binary_mat);
7388

@@ -80,12 +95,19 @@ class ColorDetector : public Detector
8095
int classifier_type;
8196

8297
bool invert_hue;
98+
bool use_lab;
8399
int min_hue;
84100
int max_hue;
85101
int min_saturation;
86102
int max_saturation;
87103
int min_value;
88104
int max_value;
105+
int min_lightness;
106+
int max_lightness;
107+
int min_a;
108+
int max_a;
109+
int min_b;
110+
int max_b;
89111

90112
std::vector<std::vector<cv::Point>> contours;
91113
std::vector<utils::Color> colors;

include/ninshiki_cpp/node/ninshiki_cpp_node.hpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,6 @@ class NinshikiCppNode
7373
std::shared_ptr<LBPDetector> lbp_detection;
7474

7575
cv::Mat received_frame;
76-
cv::Mat hsv_frame;
7776

7877
int count = 0;
7978

include/ninshiki_cpp/utils/circle.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ class Circle
3636
public:
3737
Circle(const std::vector<cv::Point> & contour);
3838

39-
void draw(cv::Mat & image, int line_size) const;
39+
void draw(cv::Mat & image, int line_size, const cv::Scalar & color = cv::Scalar(0, 255, 238)) const;
4040
cv::Mat get_binary_mat(const cv::Size & mat_size, int line_size = cv::FILLED);
4141

4242
const cv::Point2f & get_center() const;

include/ninshiki_cpp/utils/color.hpp

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,19 +29,30 @@ namespace ninshiki_cpp::utils
2929
class Color
3030
{
3131
public:
32-
Color(
33-
const std::string & name, bool invert_hue, int min_hue,
34-
int max_hue, int min_saturation, int max_saturation,
35-
int min_value, int max_value);
32+
struct Config
33+
{
34+
bool invert_hue = false;
35+
bool use_lab = false;
36+
37+
int min_hue = 0;
38+
int max_hue = 360;
39+
int min_saturation = 0;
40+
int max_saturation = 100;
41+
int min_value = 0;
42+
int max_value = 100;
43+
44+
int min_lightness = 0;
45+
int max_lightness = 255;
46+
int min_a = -128;
47+
int max_a = 127;
48+
int min_b = -128;
49+
int max_b = 127;
50+
};
51+
52+
Color(const std::string & name, const Config & config);
3653

3754
std::string name;
38-
bool invert_hue;
39-
int min_hue;
40-
int max_hue;
41-
int min_saturation;
42-
int max_saturation;
43-
int min_value;
44-
int max_value;
55+
Config config;
4556
};
4657

4758
} // namespace ninshiki_cpp::utils

launch/ninshiki_cpp_launch.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
# Copyright (c) 2024 ICHIRO ITS
2-
#
2+
#
33
# Permission is hereby granted, free of charge, to any person obtaining a copy
44
# of this software and associated documentation files (the "Software"), to deal
55
# in the Software without restriction, including without limitation the rights
66
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
77
# copies of the Software, and to permit persons to whom the Software is
88
# furnished to do so, subject to the following conditions:
9-
#
9+
#
1010
# The above copyright notice and this permission notice shall be included in
1111
# all copies or substantial portions of the Software.
12-
#
12+
#
1313
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1414
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1515
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
@@ -25,9 +25,9 @@
2525

2626
def generate_launch_description():
2727
hostname = socket.gethostname()
28-
ninshiki_config_path = os.path.expanduser(f'~/ros2-ws/configuration/{hostname}/detection/')
29-
shisen_config_path = os.path.expanduser(f'~/ros2-ws/configuration/{hostname}/camera/')
30-
28+
ninshiki_config_path = os.path.expanduser(f'~/ichiro-ws/configuration/{hostname}/detection/')
29+
shisen_config_path = os.path.expanduser(f'~/ichiro-ws/configuration/{hostname}/camera/')
30+
3131
return LaunchDescription([
3232
Node(
3333
package='shisen_cpp',

src/ninshiki_cpp/config/node/config_node.cpp

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,22 @@ ConfigNode::ConfigNode(rclcpp::Node::SharedPtr node, const std::string & path,
7272
try {
7373
utils::Color color(
7474
request->name,
75-
request->invert_hue,
76-
request->min_hue, request->max_hue,
77-
request->min_saturation, request->max_saturation,
78-
request->min_value, request->max_value
75+
utils::Color::Config{
76+
.invert_hue = request->invert_hue,
77+
.use_lab = request->use_lab,
78+
.min_hue = request->min_hue,
79+
.max_hue = request->max_hue,
80+
.min_saturation = request->min_saturation,
81+
.max_saturation = request->max_saturation,
82+
.min_value = request->min_value,
83+
.max_value = request->max_value,
84+
.min_lightness = request->min_lightness,
85+
.max_lightness = request->max_lightness,
86+
.min_a = request->min_a,
87+
.max_a = request->max_a,
88+
.min_b = request->min_b,
89+
.max_b = request->max_b,
90+
}
7991
);
8092

8193
color_detection->configure_color_setting(color);

src/ninshiki_cpp/detector/color_detector.cpp

Lines changed: 78 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -66,13 +66,22 @@ bool ColorDetector::load_configuration(const std::string & path)
6666
try {
6767
utils::Color color(
6868
item.key(),
69-
item.value().at("invert_hue").get<bool>(),
70-
item.value().at("min_hsv")[0],
71-
item.value().at("max_hsv")[0],
72-
item.value().at("min_hsv")[1],
73-
item.value().at("max_hsv")[1],
74-
item.value().at("min_hsv")[2],
75-
item.value().at("max_hsv")[2]
69+
utils::Color::Config{
70+
.invert_hue = item.value().at("invert_hue").get<bool>(),
71+
.use_lab = item.value().at("use_lab").get<bool>(),
72+
.min_hue = item.value().at("min_hsv")[0],
73+
.max_hue = item.value().at("max_hsv")[0],
74+
.min_saturation = item.value().at("min_hsv")[1],
75+
.max_saturation = item.value().at("max_hsv")[1],
76+
.min_value = item.value().at("min_hsv")[2],
77+
.max_value = item.value().at("max_hsv")[2],
78+
.min_lightness = item.value().at("min_lab")[0],
79+
.max_lightness = item.value().at("max_lab")[0],
80+
.min_a = item.value().at("min_lab")[1],
81+
.max_a = item.value().at("max_lab")[1],
82+
.min_b = item.value().at("min_lab")[2],
83+
.max_b = item.value().at("max_lab")[2]
84+
}
7685
);
7786

7887
colors.push_back(color);
@@ -97,15 +106,21 @@ bool ColorDetector::save_configuration()
97106
nlohmann::json config = nlohmann::json::array();
98107

99108
for (auto & item : colors) {
100-
bool invert_hue = item.invert_hue;
101-
int min_hsv[] = {item.min_hue, item.min_saturation, item.min_value};
102-
int max_hsv[] = {item.max_hue, item.max_saturation, item.max_value};
109+
bool invert_hue = item.config.invert_hue;
110+
bool use_lab = item.config.use_lab;
111+
int min_hsv[] = {item.config.min_hue, item.config.min_saturation, item.config.min_value};
112+
int max_hsv[] = {item.config.max_hue, item.config.max_saturation, item.config.max_value};
113+
int min_lab[] = {item.config.min_lightness, item.config.min_a, item.config.min_b};
114+
int max_lab[] = {item.config.max_lightness, item.config.max_a, item.config.max_b};
103115

104116
nlohmann::json color = {
105117
{item.name, {
106118
{"invert_hue", invert_hue},
119+
{"use_lab", use_lab},
107120
{"min_hsv", min_hsv},
108-
{"max_hsv", max_hsv}
121+
{"max_hsv", max_hsv},
122+
{"min_lab", min_lab},
123+
{"max_lab", max_lab},
109124
}}
110125
};
111126

@@ -132,13 +147,7 @@ void ColorDetector::configure_color_setting(utils::Color color)
132147
{
133148
for (auto & item : colors) {
134149
if (item.name == color.name) {
135-
item.invert_hue = color.invert_hue;
136-
item.min_hue = color.min_hue;
137-
item.max_hue = color.max_hue;
138-
item.min_saturation = color.min_saturation;
139-
item.max_saturation = color.max_saturation;
140-
item.min_value = color.min_value;
141-
item.max_value = color.max_value;
150+
item.config = color.config;
142151

143152
break;
144153
}
@@ -178,11 +187,7 @@ cv::Mat ColorDetector::classify(cv::Mat input)
178187

179188
cv::bitwise_or(mask1, mask2, output);
180189
} else {
181-
cv::inRange(input,
182-
cv::Scalar(h_min, s_min, v_min),
183-
cv::Scalar(h_max, s_max, v_max),
184-
output
185-
);
190+
cv::inRange(input, hsv_min, hsv_max, output);
186191
}
187192

188193
cv::Mat element = cv::getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(3, 3), cv::Point(1, 1));
@@ -191,6 +196,30 @@ cv::Mat ColorDetector::classify(cv::Mat input)
191196
return output;
192197
}
193198

199+
cv::Mat ColorDetector::classify_lab(cv::Mat input)
200+
{
201+
int l_min = min_lightness;
202+
int l_max = max_lightness;
203+
204+
int a_min = min_a + 128;
205+
int a_max = max_a + 128;
206+
207+
int b_min = min_b + 128;
208+
int b_max = max_b + 128;
209+
210+
cv::Scalar lab_min = cv::Scalar(l_min, a_min, b_min);
211+
cv::Scalar lab_max = cv::Scalar(l_max, a_max, b_max);
212+
213+
cv::Mat output = input.clone();
214+
215+
cv::inRange(input, lab_min, lab_max, output);
216+
217+
cv::Mat element = cv::getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(3, 3), cv::Point(1, 1));
218+
cv::morphologyEx(output, output, cv::MORPH_CLOSE, element);
219+
220+
return output;
221+
}
222+
194223
cv::Mat ColorDetector::classify_gray(cv::Mat input)
195224
{
196225
int v_min = (min_value * 255) / 100;
@@ -228,16 +257,32 @@ void ColorDetector::detection(const cv::Mat & image)
228257
// iterate every color in colors
229258
for (auto & color : colors) {
230259
color_name = color.name;
231-
invert_hue = color.invert_hue;
232-
min_hue = color.min_hue;
233-
max_hue = color.max_hue;
234-
min_saturation = color.min_saturation;
235-
max_saturation = color.max_saturation;
236-
min_value = color.min_value;
237-
max_value = color.max_value;
238-
239-
cv::Mat field_binary_mat = classify(image);
240-
find(field_binary_mat);
260+
invert_hue = color.config.invert_hue;
261+
use_lab = color.config.use_lab;
262+
min_hue = color.config.min_hue;
263+
max_hue = color.config.max_hue;
264+
min_saturation = color.config.min_saturation;
265+
max_saturation = color.config.max_saturation;
266+
min_value = color.config.min_value;
267+
max_value = color.config.max_value;
268+
min_lightness = color.config.min_lightness;
269+
max_lightness = color.config.max_lightness;
270+
min_a = color.config.min_a;
271+
max_a = color.config.max_a;
272+
min_b = color.config.min_b;
273+
max_b = color.config.max_b;
274+
275+
if (!use_lab) {
276+
cv::Mat hsv_image;
277+
cv::cvtColor(image, hsv_image, cv::COLOR_BGR2HSV);
278+
cv::Mat field_binary_mat = classify(hsv_image);
279+
find(field_binary_mat);
280+
} else {
281+
cv::Mat lab_image;
282+
cv::cvtColor(image, lab_image, cv::COLOR_BGR2Lab);
283+
cv::Mat field_binary_mat = classify_lab(lab_image);
284+
find(field_binary_mat);
285+
}
241286

242287
// Copy contours to ros2 msg
243288
if (contours.size() >= 0) {

src/ninshiki_cpp/node/ninshiki_cpp_node.cpp

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,15 +46,14 @@ NinshikiCppNode::NinshikiCppNode(
4646
image_subscriber =
4747
node->create_subscription<Image>("camera/image", 10, [this](const Image::SharedPtr message) {
4848
if (!message->data.empty()) {
49-
received_frame = cv_bridge::toCvShare(message)->image;
49+
received_frame = cv_bridge::toCvCopy(message, "bgr8")->image;
5050
}
5151
});
5252

5353
node_timer = node->create_wall_timer(
5454
std::chrono::milliseconds(frequency),
5555
[this]() {
5656
if (!received_frame.empty()) {
57-
cv::cvtColor(received_frame, hsv_frame, cv::COLOR_BGR2HSV);
5857
publish();
5958
}
6059
}
@@ -71,7 +70,7 @@ void NinshikiCppNode::publish()
7170
}
7271

7372
if (color_detection) {
74-
color_detection->detection(hsv_frame);
73+
color_detection->detection(received_frame);
7574
color_segmentation_publisher->publish(color_detection->detection_result);
7675

7776
color_detection->detection_result.contours.clear();

src/ninshiki_cpp/utils/circle.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@ Circle::Circle(const std::vector<cv::Point> & contour) : center(cv::Point2f(-1,
2828
cv::minEnclosingCircle(contour, center, radius);
2929
}
3030

31-
void Circle::draw(cv::Mat & image, int line_size) const
31+
void Circle::draw(cv::Mat & image, int line_size, const cv::Scalar & color) const
3232
{
33-
cv::circle(image, center, radius, cv::Scalar(0, 255, 238), line_size);
33+
cv::circle(image, center, radius, color, line_size);
3434
}
3535

3636
cv::Mat Circle::get_binary_mat(const cv::Size & mat_size, int line_size)

src/ninshiki_cpp/utils/color.cpp

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,8 @@
2525
namespace ninshiki_cpp::utils
2626
{
2727

28-
Color::Color(
29-
const std::string & name, bool invert_hue, int min_hue,
30-
int max_hue, int min_saturation, int max_saturation,
31-
int min_value, int max_value)
32-
: name(name), invert_hue(invert_hue), min_hue(min_hue), max_hue(max_hue),
33-
min_saturation(min_saturation), max_saturation(max_saturation),
34-
min_value(min_value), max_value(max_value)
28+
Color::Color(const std::string & name, const Config & config)
29+
: name(name), config(config)
3530
{
3631
}
3732

0 commit comments

Comments
 (0)