|
1 | 1 | # Image Filtering Node |
2 | 2 | [](https://github.com/vortexntnu/vortex-image-filtering/actions/workflows/industrial-ci.yml) |
| 3 | +[](https://results.pre-commit.ci/latest/github/vortexntnu/vortex-image-filtering/main) |
3 | 4 | [](https://codecov.io/github/vortexntnu/vortex-image-filtering) |
4 | 5 |
|
5 | 6 | The `image_filtering_node` is a ROS 2 node developed in the `vortex::image_filters` namespace. It is designed to subscribe to image topics, apply various image filters using OpenCV, and publish the filtered images back to ROS. |
@@ -31,96 +32,162 @@ Parameters can be set through a YAML file or dynamically adjusted at runtime. |
31 | 32 |
|
32 | 33 | ## Implementing New Filters |
33 | 34 |
|
34 | | -To extend the functionality of the `image_filtering_node` by adding new filters, follow these steps to ensure compatibility and integration with the existing codebase: |
| 35 | +To extend the functionality of the `image_filtering_node` by adding new filters, follow these steps to ensure compatibility and integration with the existing codebase. There should be //TODO(New filter) comments where you add your filter: |
35 | 36 |
|
36 | | -### Step 1: Define Filter Parameters |
| 37 | +### Step 1: Filter Enum |
37 | 38 |
|
38 | | -Each filter should have its own set of parameters encapsulated in a structure. Define this structure within the `vortex::image_filters` namespace. |
| 39 | +You should define your filtertype in the filtertype enum in [typedef.hpp](image-filtering/include/lib/typedef.hpp) |
39 | 40 |
|
40 | 41 | ```cpp |
41 | | -struct YourFilterParams { |
42 | | - // Add necessary parameters here |
43 | | - int example_param; |
| 42 | +enum class FilterType { |
| 43 | + NoFilter, |
| 44 | + Flip, |
| 45 | + Unsharpening, |
| 46 | + Erosion, |
| 47 | + Dilation, |
| 48 | + ... |
| 49 | + // Add your filter here |
44 | 50 | }; |
45 | 51 | ``` |
46 | | -
|
47 | | -### Step 2: Add to FilterParams Structure |
48 | | -
|
49 | | -Integrate your new filter parameters structure into the existing `FilterParams` structure. This allows the `apply_filter` function to access the parameters specific to your filter. |
| 52 | +To access the filter through the yaml file we need to access it through a string. In the same file you need to add it as an item in the kFilterMap. |
50 | 53 |
|
51 | 54 | ```cpp |
52 | | -struct FilterParams { |
53 | | - std::string filter_type; |
54 | | - UnsharpeningFilterParams unsharpening; |
55 | | - ErodingFilterParams eroding; |
56 | | - DilatingFilterParams dilating; |
57 | | - WhiteBalancingFilterParams white_balancing; |
58 | | - EbusFilterParams ebus; |
59 | | - YourFilterParams your_filter; // Add your filter params here |
| 55 | +static constexpr std::pair<std::string_view, FilterType> kFilterMap[] = { |
| 56 | + {"no_filter", FilterType::NoFilter}, |
| 57 | + {"flip", FilterType::Flip}, |
| 58 | + {"unsharpening", FilterType::Unsharpening}, |
| 59 | + ... |
| 60 | +
|
| 61 | + // Add your filter here |
| 62 | + {"example", FilterType::Example}, |
| 63 | + {"unknown", FilterType::Unknown} |
60 | 64 | }; |
61 | 65 | ``` |
62 | 66 |
|
63 | | -### Step 3: Create the Filter Function |
| 67 | +### Step 2: Make the filter header |
| 68 | +Each filter should have its own headerfile asosiated with it. You can add this in the [filters](image-filtering/include/lib/filters), and name it the same as your filter (your_filter.hpp). In this file you start with adding these lines (swapping out example with your filter): |
64 | 69 |
|
65 | | -Implement your filter function. This function should take the `cv::Mat` objects for the input and output images and a `const FilterParams&` which includes your specific filter parameters. Make sure to use your parameter structure within this function. |
| 70 | +```cpp |
| 71 | +#ifndef LIB__FILTERS__EXAMPLE_HPP_ |
| 72 | +#define LIB__FILTERS__EXAMPLE_HPP_ |
| 73 | +#include "abstract_filter_class.hpp" |
66 | 74 |
|
| 75 | + |
| 76 | +// Insert code here ... |
| 77 | + |
| 78 | + |
| 79 | +#endif // LIB__FILTERS__EXAMPLE_HPP_ |
| 80 | +``` |
| 81 | +This new file needs to be added to [all_filters.hpp](image-filtering/include/lib/filters/all_filters.hpp). |
67 | 82 | ```cpp |
68 | | -void your_filter_function(const cv::Mat &original, cv::Mat &filtered, const FilterParams& filter_params) { |
69 | | - // Access your filter-specific parameters like this: |
70 | | - int example_param = filter_params.your_filter.example_param; |
| 83 | +#ifndef LIB__FILTERS__EXAMPLE_HPP_ |
| 84 | +#define LIB__FILTERS__EXAMPLE_HPP_ |
71 | 85 |
|
72 | | - // Implement your filtering logic here |
73 | | -} |
| 86 | +// Add your filter to the top of this file: |
| 87 | +#include "lib/filters/your_filter.hpp" |
| 88 | + |
| 89 | +#include "lib/filters/example.hpp" |
| 90 | +#include "lib/filters/no_filter.hpp" |
| 91 | + |
| 92 | + |
| 93 | +#endif // LIB__FILTERS__EXAMPLE_HPP_ |
74 | 94 | ``` |
75 | 95 |
|
76 | | -### Step 4: Register the Filter Function |
77 | 96 |
|
78 | | -Add an entry to the `filter_functions` map for your new filter. This step is crucial as it links the filter name (as a string) to the corresponding filter function pointer. |
| 97 | +### Step 3: Define Filter Parameters |
| 98 | + |
| 99 | +Each filter should have its own set of parameters encapsulated in a structure. Define this structure within your_filter.hpp. |
79 | 100 |
|
80 | 101 | ```cpp |
81 | | -std::map<std::string, FilterFunction> filter_functions = { |
82 | | - {"no_filter", no_filter}, |
83 | | - {"sharpening", sharpening_filter}, |
84 | | - {"unsharpening", unsharpening_filter}, |
85 | | - {"eroding", eroding_filter}, |
86 | | - {"dilating", dilating_filter}, |
87 | | - {"white_balancing", white_balance_filter}, |
88 | | - {"ebus", ebus_filter}, |
89 | | - {"your_filter", your_filter_function} // Add your filter here |
| 102 | +struct ExampleParams{ |
| 103 | + // Add necessary filter parameters here |
| 104 | + int example_int; |
| 105 | + std::string example_string; |
90 | 106 | }; |
91 | 107 | ``` |
92 | 108 |
|
93 | | -### Step 5: Declare and Assign Parameters |
| 109 | +### Step 4: Add filter class |
| 110 | + |
| 111 | +Below the filter parameters add a Class for your filter inheriting from the Filter class, with the same structure as shown below. |
| 112 | +```cpp |
| 113 | +class Example: public Filter{ |
| 114 | + public: |
| 115 | + explicit Example(ExampleParams params): filter_params(params) {} |
| 116 | + void apply_filter(const cv::Mat& original, cv::Mat& filtered) const override; // This is the filter itself |
| 117 | + private: |
| 118 | + ExampleParams filter_params; |
| 119 | +}; |
| 120 | +``` |
| 121 | +Here you can add other filter specific stuff like storing variables that need to change between runs and so on. |
94 | 122 |
|
95 | | -Declare the new filter parameters in the ROS 2 node constructor and assign these parameters to the `FilterParams` structure within the `set_filter_params` function. |
96 | 123 |
|
97 | | -#### In the Node Constructor |
| 124 | +### Step 5: Define the filter function |
98 | 125 |
|
99 | | -In the constructor of your ROS 2 node, declare each of the new filter parameters using the `declare_parameter` function. This sets the default values and prepares the node to accept these parameters at runtime through command line or a YAML configuration file. |
| 126 | +You can do this in two different ways. If your filter is big you can add a cpp file for your filter, explained in the [helperfunctions](#helper-functions) section of this page. Otherwise you can add the function definition just below the class definition like this. |
100 | 127 |
|
101 | 128 | ```cpp |
102 | | -ImageFilteringNode::ImageFilteringNode() : Node("image_filtering_node") |
103 | | -{ |
104 | | - this->declare_parameter<std::string>("filter_params.your_filter.example_param", "default_value"); |
105 | | - ... |
106 | | - // Other parameters declarations |
| 129 | +inline void Example::apply_filter(const cv::Mat& original, cv::Mat& filtered) const{ |
| 130 | + std::string example_str = this->filter_params.example_string; |
| 131 | + int example_int = this->filter_params.example_int; |
| 132 | + DoExample(original,filtered, example_str, example_int); |
107 | 133 | } |
108 | 134 | ``` |
109 | 135 |
|
110 | 136 |
|
111 | | -#### In the set_filter_params Function |
| 137 | +### Step 6: Add to config file |
112 | 138 |
|
113 | | -In the set_filter_params function, retrieve and assign the parameters to the corresponding fields in the FilterParams structure. Ensure to handle cases where the parameter might not be set or provided. |
| 139 | +In the [image_filtering_params.yaml](image-filtering/config/image_filtering_params.yaml) file you add your filter and filterparameters for easily interfacing with the filters: |
114 | 140 |
|
115 | | -```cpp |
| 141 | +```yaml |
| 142 | + filter_params: |
| 143 | + filter_type: "example" |
116 | 144 |
|
117 | | -void ImageFilteringNode::set_filter_params(){ |
118 | | - FilterParams params = filter_params_; // assuming filter_params_ is already defined in your class |
| 145 | + flip: |
| 146 | + flip_code: 1 |
| 147 | + ... |
| 148 | + # Add your filter type here |
| 149 | +
|
| 150 | + example: |
| 151 | + example_int: 5 |
| 152 | + example_string: "This is an example" |
| 153 | +``` |
119 | 154 |
|
120 | | - params.your_filter.example_param = this->get_parameter("filter_params.your_filter.example_param").as_string(); |
| 155 | + |
| 156 | +### Step 7: Declare and Assign Parameters |
| 157 | + |
| 158 | +Now we need to use the right filter and set the variables for your filter. We do that by making a new case in `set_filter_params`, in [image_filtering_ros.cpp](image-filtering/src/ros/image_filtering_ros.cpp), for your filter. |
| 159 | + |
| 160 | +```cpp |
| 161 | +void ImageFilteringNode::set_filter_params() { |
121 | 162 | ... |
122 | | - // Retrieve other parameters and handle cases where parameters might not be provided |
123 | | - filter_params_ = params; // Update the filter parameters structure |
124 | | - RCLCPP_INFO(this->get_logger(), "Filter parameters updated for your_filter."); |
| 163 | + switch (filter_type){ |
| 164 | + |
| 165 | + ... |
| 166 | + |
| 167 | + case FilterType::Example: { |
| 168 | + ExampleParams params; |
| 169 | + params.example_int = |
| 170 | + declare_and_get<int>("filter_params.example.example_int"); |
| 171 | + params.example_string = |
| 172 | + declare_and_get<std::string>( |
| 173 | + "filter_params.example.example_string"); |
| 174 | + |
| 175 | + filter_ptr = std::make_unique<Example>(params); |
| 176 | + break; |
| 177 | + } |
| 178 | + } |
125 | 179 | } |
126 | 180 | ``` |
| 181 | + |
| 182 | + |
| 183 | + |
| 184 | +### Helper functions |
| 185 | + |
| 186 | +#### Define the apply filter function |
| 187 | + |
| 188 | +If the YourFilter::apply_filter function gets to big you can add a cpp file named the same as your hpp file (your_filter.cpp) in the [filters](image-filtering/src/lib/filters) folder. There you add #include "lib/filters/your_filter.hpp" at the top. Then you can define your filter there. Whenever you make a new c++ file, add the path to [CMakeList.txt](image-filtering/CMakeLists.txt), to the `add_library(${CORE_LIB} SHARED` part. |
| 189 | + |
| 190 | +#### Make and use helperfunctions |
| 191 | + |
| 192 | + |
| 193 | +If you are making a function that is useful for many different filters, then you can add the declaration to [utilities.hpp](image-filtering/include/lib/utilities.hpp), and the definition to [utilities.cpp](image-filtering/src/lib/utilities.cpp). Make a description off the function above the declaration. To use this in the filter add `#include "image-filtering/include/lib/utilities.hpp"` to your_filter.hpp |
0 commit comments