Skip to content

Commit 0a99fe0

Browse files
committed
-add ros2 support to face mesh
1 parent 6c19efb commit 0a99fe0

File tree

7 files changed

+301
-48
lines changed

7 files changed

+301
-48
lines changed

.github/workflows/code_compiles.yml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
name: code_compiles
2+
3+
on:
4+
push:
5+
branches: ["main"]
6+
pull_request:
7+
branches: ["main"]
8+
workflow_dispatch: {}
9+
10+
jobs:
11+
build:
12+
# The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac.
13+
# You can convert this to a matrix build if you need cross-platform coverage.
14+
# See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix
15+
runs-on: ubuntu-24.04
16+
17+
steps:
18+
- uses: actions/checkout@v3
19+
20+
- name: Install prerequired packages
21+
# To compile and run the code we require cmake, ninja and opencv
22+
run: sudo apt-get update && sudo apt-get install build-essential cmake ninja-build libopencv-dev
23+
24+
- name: Configure CMake
25+
# Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make.
26+
# See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type
27+
run: cmake -B ${{github.workspace}}/build -G Ninja
28+
29+
- name: Build
30+
# Build your program with the given configuration
31+
run: cmake --build ${{github.workspace}}/build
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
name: ros_code_compiles
2+
3+
on:
4+
push:
5+
branches: ["main"]
6+
pull_request:
7+
branches: ["main"]
8+
workflow_dispatch: {}
9+
10+
jobs:
11+
build:
12+
# The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac.
13+
# You can convert this to a matrix build if you need cross-platform coverage.
14+
# See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix
15+
runs-on: ubuntu-24.04
16+
17+
steps:
18+
- uses: actions/checkout@v3
19+
- uses: ros-tooling/setup-ros@v0.7
20+
with:
21+
required-ros-distributions: jazzy
22+
23+
- name: Install prerequired packages
24+
# To compile and run the code we require cmake, ninja and opencv
25+
run: sudo apt-get update && sudo apt-get install build-essential cmake ninja-build libopencv-dev
26+
27+
- name: Install dependencies
28+
working-directory: ${{ github.workspace }}
29+
run: |
30+
sudo rosdep init || true
31+
rosdep update
32+
rosdep install --from-paths src -y --ignore-src
33+
34+
- name: Colcon build
35+
working-directory: ${{ github.workspace }}
36+
run: |
37+
source /opt/ros/jazzy/setup.bash
38+
colcon build --packages-select face_detector

CMakeLists.txt

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ set(CLFML_FACE_MESH_MAIN_PROJECT OFF)
88
option(CLFML_FACE_MESH_BUILD_EXAMPLE_PROJECTS "Build example projects" ON)
99

1010
# Optionally enable ROS2 package build (Requires ROS2 jazzy to be installed!)
11-
option(CLFML_ROS2_PACKAGE_BUILD "Build a ROS2 package" OFF)
11+
option(CLFML_ROS2_PACKAGE_BUILD "Build a ROS2 package" ON)
1212

1313
if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
1414
set(CMAKE_CXX_STANDARD 17)
@@ -111,18 +111,6 @@ target_compile_definitions(${PROJECT_NAME} PUBLIC -DCLFML_FACE_MESH_CPU_MODEL_PA
111111

112112
set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 17)
113113

114-
if(CLFML_FACE_MESH_BUILD_EXAMPLE_PROJECTS)
115-
FetchContent_Declare(
116-
face_detector.cpp
117-
GIT_REPOSITORY https://github.com/CLFML/Face_Detector.Cpp.git
118-
GIT_TAG main
119-
)
120-
FetchContent_MakeAvailable(face_detector.cpp)
121-
add_executable(face_mesh_demo ${CMAKE_CURRENT_LIST_DIR}/example/face_mesh_demo/demo.cpp)
122-
target_link_libraries(face_mesh_demo PUBLIC CLFML::${PROJECT_NAME} CLFML::face_detector)
123-
124-
endif()
125-
126114
if (CLFML_ROS2_PACKAGE_BUILD)
127115
find_package(ament_cmake REQUIRED)
128116
find_package(rclcpp REQUIRED)
@@ -148,6 +136,35 @@ if (CLFML_ROS2_PACKAGE_BUILD)
148136
DESTINATION lib/${PROJECT_NAME})
149137
endif()
150138

139+
140+
if(CLFML_FACE_MESH_BUILD_EXAMPLE_PROJECTS)
141+
FetchContent_Declare(
142+
face_detector.cpp
143+
GIT_REPOSITORY https://github.com/CLFML/Face_Detector.Cpp.git
144+
GIT_TAG main
145+
)
146+
FetchContent_MakeAvailable(face_detector.cpp)
147+
if (CLFML_ROS2_PACKAGE_BUILD)
148+
add_executable(face_mesh_viewer ${CMAKE_CURRENT_LIST_DIR}/example/ros2/face_mesh_viewer.cpp)
149+
ament_target_dependencies(face_mesh_viewer
150+
rclcpp
151+
sensor_msgs
152+
cv_bridge
153+
std_msgs
154+
geometry_msgs
155+
std_srvs
156+
OpenCV
157+
)
158+
install(TARGETS
159+
face_mesh_viewer
160+
DESTINATION lib/${PROJECT_NAME})
161+
else ()
162+
add_executable(face_mesh_demo ${CMAKE_CURRENT_LIST_DIR}/example/face_mesh_demo/demo.cpp)
163+
target_link_libraries(face_mesh_demo PUBLIC CLFML::${PROJECT_NAME} CLFML::face_detector)
164+
endif()
165+
endif()
166+
167+
151168
if (CLFML_ROS2_PACKAGE_BUILD)
152169
install(FILES bindings/ros2/set_ld_path.sh DESTINATION lib/${PROJECT_NAME})
153170
ament_package()

README.md

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,14 +77,65 @@ add_subdirectory(Face_Mesh.Cpp)
7777
...
7878
target_link_libraries(YOUR_EXECUTABLE CLFML::face_mesh)
7979
```
80+
81+
## Building a ROS2 package with Colcon
82+
Before using this library you will need the following packages installed:
83+
- OpenCV
84+
- ROS2
85+
- ROS CV bridge
86+
- Working C++ compiler (GCC or Clang)
87+
- CMake
88+
89+
### Running the examples (Ubuntu, CPU)
90+
91+
1. Clone this repo:
92+
```
93+
git clone https://github.com/CLFML/Face_Mesh.Cpp.git
94+
```
95+
96+
2. Source your ROS2 installation:
97+
98+
```bash
99+
source /opt/ros/jazzy/setup.bash
100+
```
101+
102+
3. Install the dependencies:
103+
```bash
104+
rosdep install --from-paths src -y --ignore-src
105+
```
106+
107+
4. Build the package:
108+
109+
```bash
110+
colcon build --packages-select face_mesh
111+
```
112+
113+
5. Set up the environment:
114+
115+
```bash
116+
source install/setup.bash
117+
```
118+
119+
6. Run the camera node:
120+
121+
```bash
122+
ros2 run v4l2_camera v4l2_camera_node
123+
```
124+
125+
7. In another terminal, run the nodes
126+
127+
```bash
128+
ros2 launch example/ros2/launch.py
129+
```
130+
131+
80132
## Aditional documentation
81133
See our [wiki](https://clfml.github.io/Face_Mesh.Cpp/)...
82134

83135
## Todo
84136
- Add language bindings for Python, C# and Java
85137
- Add support for MakeFiles and Bazel
86138
- Add Unit-tests
87-
- Add ROS2 package support
88139
- Add support for the [Face Mesh V2 model](https://storage.googleapis.com/mediapipe-assets/Model%20Card%20MediaPipe%20Face%20Mesh%20V2.pdf)
89140

90141
## License

bindings/ros2/face_mesh_node.cpp

Lines changed: 29 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,12 @@
2020
*
2121
*/
2222
#include "rclcpp/rclcpp.hpp"
23-
#include "sensor_msgs/msg/image.hpp"
24-
#include "sensor_msgs/msg/region_of_interest.hpp"
2523
#include "cv_bridge/cv_bridge.hpp"
2624
#include "std_msgs/msg/int32.hpp"
2725
#include "sensor_msgs/msg/point_cloud2.hpp"
2826
#include "sensor_msgs/point_cloud2_iterator.hpp"
27+
#include "sensor_msgs/msg/image.hpp"
28+
#include "sensor_msgs/msg/region_of_interest.hpp"
2929
#include "std_srvs/srv/set_bool.hpp"
3030
#include "face_mesh.hpp"
3131

@@ -43,38 +43,27 @@ class FaceMeshNode : public rclcpp::Node
4343
private:
4444
void declare_and_get_parameters()
4545
{
46-
declare_parameter("run_on_demand", false);
47-
declare_parameter("trigger_topic", "/face_detected");
46+
declare_parameter("roi_topic", "/face_roi");
4847
declare_parameter("camera_topic", "/image_raw");
4948
declare_parameter("face_mesh_landmarks_topic", "/face_mesh_landmarks");
5049

5150
camera_topic_ = get_parameter("camera_topic").as_string();
5251
face_landmarks_topic_ = get_parameter("face_mesh_landmarks_topic").as_string();
53-
on_demand_topic_ = get_parameter("trigger_topic").as_string();
54-
run_continuous_ = !get_parameter("run_on_demand").as_bool();
52+
roi_topic_ = get_parameter("roi_topic").as_string();
5553
}
5654

5755
void setup_communication()
5856
{
59-
if (run_continuous_)
60-
{
61-
// Link process image straight to the incoming image callback
62-
image_sub_ = create_subscription<sensor_msgs::msg::Image>(
63-
camera_topic_, rclcpp::SensorDataQoS(),
64-
std::bind(&FaceMeshNode::image_callback, this, std::placeholders::_1));
65-
}
66-
else
67-
{
68-
// Subscribe to the on-demand trigger topic
69-
on_demand_sub_ = create_subscription<std_msgs::msg::Int32>(
70-
on_demand_topic_, 10,
71-
std::bind(&FaceMeshNode::on_demand_callback, this, std::placeholders::_1));
72-
73-
// Subscribe to the image topic but only store the latest image
74-
image_sub_ = create_subscription<sensor_msgs::msg::Image>(
75-
camera_topic_, rclcpp::SensorDataQoS(),
76-
std::bind(&FaceMeshNode::store_latest_image, this, std::placeholders::_1));
77-
}
57+
// Subscribe to the on-demand trigger topic
58+
on_demand_sub_ = create_subscription<sensor_msgs::msg::RegionOfInterest>(
59+
roi_topic_, 10,
60+
std::bind(&FaceMeshNode::on_demand_callback, this, std::placeholders::_1));
61+
62+
// Subscribe to the image topic but only store the latest image
63+
image_sub_ = create_subscription<sensor_msgs::msg::Image>(
64+
camera_topic_, rclcpp::SensorDataQoS(),
65+
std::bind(&FaceMeshNode::store_latest_image, this, std::placeholders::_1));
66+
7867
face_mesh_landmarks_pub_ = create_publisher<sensor_msgs::msg::PointCloud2>(face_landmarks_topic_, 10);
7968

8069
toggle_service_ = create_service<std_srvs::srv::SetBool>(
@@ -97,7 +86,9 @@ class FaceMeshNode : public rclcpp::Node
9786
RCLCPP_ERROR(get_logger(), "cv_bridge exception: %s", e.what());
9887
return;
9988
}
100-
det_.load_image(cv_ptr->image);
89+
90+
cv::Mat cropped_image_to_roi = cv_ptr->image(roi_);
91+
det_.load_image(cropped_image_to_roi, roi_);
10192

10293
publish_face_mesh_landmarks();
10394
}
@@ -106,16 +97,20 @@ class FaceMeshNode : public rclcpp::Node
10697
last_image_msg_ = msg; // Store the latest image in memory
10798
}
10899

109-
void on_demand_callback(const std_msgs::msg::Int32::SharedPtr msg)
100+
void on_demand_callback(const sensor_msgs::msg::RegionOfInterest::SharedPtr msg)
110101
{
111-
if (!detection_enabled_ || msg->data == 0)
102+
if (!detection_enabled_ || msg->width == 0 || msg->height == 0)
112103
return;
113104

114-
RCLCPP_INFO(get_logger(), "Trigger received! Processing latest image...");
115-
116105
if (last_image_msg_)
117106
{
118-
image_callback(last_image_msg_); // Process the last stored image
107+
roi_ = cv::Rect(msg->x_offset, msg->y_offset, msg->width, msg->height);
108+
if (roi_.x >= 0 && roi_.y >= 0 &&
109+
roi_.x + roi_.width <= last_image_msg_->width &&
110+
roi_.y + roi_.height <= last_image_msg_->height)
111+
{
112+
image_callback(last_image_msg_); // Process the last stored image
113+
}
119114
}
120115
else
121116
{
@@ -179,7 +174,7 @@ class FaceMeshNode : public rclcpp::Node
179174
}
180175

181176
rclcpp::Subscription<sensor_msgs::msg::Image>::SharedPtr image_sub_;
182-
rclcpp::Subscription<std_msgs::msg::Int32>::SharedPtr on_demand_sub_;
177+
rclcpp::Subscription<sensor_msgs::msg::RegionOfInterest>::SharedPtr on_demand_sub_;
183178
rclcpp::Publisher<sensor_msgs::msg::PointCloud2>::SharedPtr face_mesh_landmarks_pub_;
184179
rclcpp::Service<std_srvs::srv::SetBool>::SharedPtr toggle_service_;
185180

@@ -188,9 +183,9 @@ class FaceMeshNode : public rclcpp::Node
188183
CLFML::FaceMesh::FaceMesh det_;
189184
std::string camera_topic_;
190185
std::string face_landmarks_topic_;
191-
std::string on_demand_topic_;
186+
std::string roi_topic_;
187+
cv::Rect roi_ = cv::Rect(0, 0, 0, 0);
192188
bool detection_enabled_;
193-
bool run_continuous_;
194189
};
195190

196191
int main(int argc, char **argv)

0 commit comments

Comments
 (0)