Skip to content

Commit bd38bbe

Browse files
authored
Add support for freeform models, move printing to the SDK (#29)
* Add support for freeform models, move printing to the SDK * Check return code of ei_set_freeform_output, upstream latest eim.cpp * Freeform helper * OpenCV 4.7, switch name for freeform init function
1 parent de38c45 commit bd38bbe

File tree

10 files changed

+366
-266
lines changed

10 files changed

+366
-266
lines changed

Makefile

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ CFLAGS += -Ithird_party/
1212
CFLAGS += -Iutils/
1313
CFLAGS += -Os
1414
CFLAGS += -DNDEBUG
15-
CFLAGS += -DEI_CLASSIFIER_ENABLE_DETECTION_POSTPROCESS_OP=1
1615
CFLAGS += -g
1716
ifeq (${CC}, clang)
1817
CFLAGS += -Wno-asm-operand-widths

build-opencv-linux-aarch64-cross-compile.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,11 @@ cd $OPENCV_DIR
2727

2828
if [ ! -d "opencv" ]; then
2929
git clone https://github.com/opencv/opencv.git
30-
cd opencv && git checkout 69357b1e88680658a07cffde7678a4d697469f03 && cd .. # v4.5.2
30+
cd opencv && git checkout 4.7.0 && cd ..
3131
fi
3232
if [ ! -d "opencv_contrib" ]; then
3333
git clone https://github.com/opencv/opencv_contrib.git
34-
cd opencv_contrib && git checkout f5d7f6712d4ff229ba4f45cf79dfd11c557d56fd && cd ..
34+
cd opencv_contrib && git checkout 4.7.0 && cd ..
3535
fi
3636

3737
CMAKE_ARGS="

build-opencv-linux.sh

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,15 @@ cd $OPENCV_DIR
1111

1212
if [ ! -d "opencv" ]; then
1313
git clone https://github.com/opencv/opencv.git
14-
cd opencv && git checkout 69357b1e88680658a07cffde7678a4d697469f03 && cd .. # v4.5.2
14+
cd opencv && git checkout 4.7.0 && cd ..
1515
fi
1616
if [ ! -d "opencv_contrib" ]; then
1717
git clone https://github.com/opencv/opencv_contrib.git
18-
cd opencv_contrib && git checkout f5d7f6712d4ff229ba4f45cf79dfd11c557d56fd && cd ..
18+
cd opencv_contrib && git checkout 4.7.0 && cd ..
1919
fi
2020

2121
mkdir -p build_opencv
2222
cd build_opencv
2323
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_EXAMPLES=ON ../opencv
24-
make -j3
24+
make -j`nproc`
2525
sudo make install

inc/bitmap_helper.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ int create_bitmap_file(const char *filename, uint16_t *buffer, size_t w, size_t
5858
}
5959
fwrite(bmpfileheader, 1, 14, f);
6060
fwrite(bmpinfoheader, 1, 40, f);
61-
for(int i = 0; i < h; i++) {
61+
for(int i = 0; i < (int)h; i++) {
6262
fwrite(img + (w * (h - i - 1) * 3), 3, w, f);
6363
fwrite(bmppad, 1, (4 - (w * 3) % 4 ) % 4, f);
6464
}
@@ -118,7 +118,7 @@ int create_bitmap_file(const char *filename, float *buffer, size_t w, size_t h)
118118
}
119119
fwrite(bmpfileheader, 1, 14, f);
120120
fwrite(bmpinfoheader, 1, 40, f);
121-
for(int i = 0; i < h; i++) {
121+
for(int i = 0; i < (int)h; i++) {
122122
fwrite(img + (w * (h - i - 1) * 3), 3, w, f);
123123
fwrite(bmppad, 1, (4 - (w * 3) % 4 ) % 4, f);
124124
}

inc/freeform_output_helper.h

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/* The Clear BSD License
2+
*
3+
* Copyright (c) 2025 EdgeImpulse Inc.
4+
* All rights reserved.
5+
*
6+
* Redistribution and use in source and binary forms, with or without
7+
* modification, are permitted (subject to the limitations in the disclaimer
8+
* below) provided that the following conditions are met:
9+
*
10+
* * Redistributions of source code must retain the above copyright notice,
11+
* this list of conditions and the following disclaimer.
12+
*
13+
* * Redistributions in binary form must reproduce the above copyright
14+
* notice, this list of conditions and the following disclaimer in the
15+
* documentation and/or other materials provided with the distribution.
16+
*
17+
* * Neither the name of the copyright holder nor the names of its
18+
* contributors may be used to endorse or promote products derived from this
19+
* software without specific prior written permission.
20+
*
21+
* NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY
22+
* THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
23+
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24+
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
25+
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
26+
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
27+
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
28+
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
29+
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
30+
* IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
31+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32+
* POSSIBILITY OF SUCH DAMAGE.
33+
*/
34+
35+
#ifndef _FREEFORM_OUTPUT_HELPER_H_
36+
#define _FREEFORM_OUTPUT_HELPER_H_
37+
38+
#include <vector>
39+
#include <stdio.h>
40+
#include "edge-impulse-sdk/classifier/ei_model_types.h"
41+
#include "edge-impulse-sdk/classifier/ei_run_classifier.h"
42+
43+
#if EI_CLASSIFIER_FREEFORM_OUTPUT
44+
45+
static std::map<ei_impulse_handle_t *, std::vector<matrix_t>> freeform_output_map;
46+
47+
/**
48+
* For "freeform" outputs, the application needs to allocate the memory (one matrix_t per output tensor).
49+
* Here we'll use a global map (one per impulse handle) to allocate this memory
50+
* (on the heap, using a matrix_t wrapper). In your own application you can just use something like this:
51+
*
52+
* std::vector<matrix_t> freeform_outputs;
53+
* freeform_outputs.reserve(ei_default_impulse.impulse->freeform_outputs_size);
54+
* for (size_t ix = 0; ix < ei_default_impulse.impulse->freeform_outputs_size; ++ix) {
55+
* freeform_outputs.emplace_back(ei_default_impulse.impulse->freeform_outputs[ix], 1);
56+
* }
57+
* EI_IMPULSE_ERROR set_freeform_res = ei_set_freeform_output(freeform_outputs.data(), freeform_outputs.size());
58+
* if (set_freeform_res != EI_IMPULSE_OK) {
59+
* printf("ei_set_freeform_output failed with %d\n", set_freeform_res);
60+
* exit(1);
61+
* }
62+
*
63+
* Where the memory is fully owned by your application (when freeform_outputs goes out of scope ->
64+
* memory is freed).
65+
*/
66+
void freeform_outputs_init(ei_impulse_handle_t *impulse_handle) {
67+
// make new object in the map based on handle (so this works with multiple impulses)
68+
auto& freeform_outputs = freeform_output_map[impulse_handle];
69+
70+
// reserve memory using the matrix_t (allocs on the heap)
71+
freeform_outputs.reserve(impulse_handle->impulse->freeform_outputs_size);
72+
for (size_t ix = 0; ix < impulse_handle->impulse->freeform_outputs_size; ++ix) {
73+
freeform_outputs.emplace_back(impulse_handle->impulse->freeform_outputs[ix], 1);
74+
}
75+
// and set the freeform output
76+
EI_IMPULSE_ERROR set_freeform_res = ei_set_freeform_output(freeform_outputs.data(), freeform_outputs.size());
77+
if (set_freeform_res != EI_IMPULSE_OK) {
78+
printf("ei_set_freeform_output failed with %d\n", set_freeform_res);
79+
exit(1);
80+
}
81+
}
82+
83+
#else
84+
85+
void freeform_outputs_init(ei_impulse_handle_t *impulse_handle) { }
86+
87+
#endif // EI_CLASSIFIER_FREEFORM_OUTPUT
88+
89+
#endif // _FREEFORM_OUTPUT_HELPER_H_

source/audio.cpp

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
#include <signal.h>
4343
#include "edge-impulse-sdk/classifier/ei_run_classifier.h"
4444
#include <alsa/asoundlib.h>
45+
#include "inc/freeform_output_helper.h"
4546

4647
// Forward declarations
4748
int microphone_audio_signal_get_data(size_t, size_t, float *);
@@ -240,14 +241,8 @@ void classify_current_buffer() {
240241
return;
241242
}
242243

243-
printf("%d ms. ", result.timing.dsp + result.timing.classification);
244-
for (size_t ix = 0; ix < EI_CLASSIFIER_LABEL_COUNT; ix++) {
245-
printf("%s: %.05f", result.classification[ix].label, result.classification[ix].value);
246-
if (ix != EI_CLASSIFIER_LABEL_COUNT - 1) {
247-
printf(", ");
248-
}
249-
}
250-
printf("\n");
244+
// Print results, see edge-impulse-sdk/classifier/ei_print_results.h
245+
ei_print_results(&ei_default_impulse, &result);
251246
}
252247

253248
/**
@@ -284,6 +279,9 @@ int main(int argc, char **argv)
284279

285280
run_classifier_init();
286281

282+
// Freeform models need to reserve their own memory. Set it up (see inc/freeform_output_helper.h)
283+
freeform_outputs_init(&ei_default_impulse);
284+
287285
while (1) {
288286
int x = snd_pcm_readi(capture_handle, classifier_slice_buffer, EI_CLASSIFIER_SLICE_SIZE);
289287
if (x != EI_CLASSIFIER_SLICE_SIZE) {

source/camera.cpp

Lines changed: 6 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
#include "opencv2/videoio/videoio_c.h"
3838
#include "edge-impulse-sdk/classifier/ei_run_classifier.h"
3939
#include "iostream"
40+
#include "inc/freeform_output_helper.h"
4041

4142
static bool use_debug = false;
4243

@@ -100,6 +101,9 @@ int main(int argc, char** argv) {
100101

101102
run_classifier_init();
102103

104+
// Freeform models need to reserve their own memory. Set it up (see inc/freeform_output_helper.h)
105+
freeform_outputs_init(&ei_default_impulse);
106+
103107
// open the webcam...
104108
cv::VideoCapture camera(atoi(argv[1]));
105109
if (!camera.isOpened()) {
@@ -150,54 +154,8 @@ int main(int argc, char** argv) {
150154
return 1;
151155
}
152156

153-
// print the predictions
154-
printf("Predictions (DSP: %d ms., Classification: %d ms., Anomaly: %d ms.): \n",
155-
result.timing.dsp, result.timing.classification, result.timing.anomaly);
156-
157-
#if EI_CLASSIFIER_OBJECT_TRACKING_ENABLED == 1
158-
printf("#Object tracking results:\n");
159-
for (uint32_t ix = 0; ix < result.postprocessed_output.object_tracking_output.open_traces_count; ix++) {
160-
ei_object_tracking_trace_t trace = result.postprocessed_output.object_tracking_output.open_traces[ix];
161-
printf("%s (ID %d) [ x: %u, y: %u, width: %u, height: %u ]\n", trace.label, (int)trace.id, trace.x, trace.y, trace.width, trace.height);
162-
}
163-
164-
if (result.postprocessed_output.object_tracking_output.open_traces_count == 0) {
165-
printf(" No objects found\n");
166-
}
167-
#elif EI_CLASSIFIER_OBJECT_DETECTION == 1
168-
printf("#Object detection results:\n");
169-
bool found_bb = false;
170-
for (size_t ix = 0; ix < result.bounding_boxes_count; ix++) {
171-
auto bb = result.bounding_boxes[ix];
172-
if (bb.value == 0) {
173-
continue;
174-
}
175-
176-
found_bb = true;
177-
printf(" %s (%f) [ x: %u, y: %u, width: %u, height: %u ]\n", bb.label, bb.value, bb.x, bb.y, bb.width, bb.height);
178-
}
179-
180-
if (!found_bb) {
181-
printf(" no objects found\n");
182-
}
183-
#else
184-
printf("#Classification results:\n");
185-
for (size_t ix = 0; ix < EI_CLASSIFIER_LABEL_COUNT; ix++) {
186-
printf("%s: %.05f\n", result.classification[ix].label, result.classification[ix].value);
187-
}
188-
#endif
189-
190-
#if EI_CLASSIFIER_HAS_VISUAL_ANOMALY // visual AD
191-
printf("#Visual anomaly grid results:\n");
192-
for (uint32_t i = 0; i < result.visual_ad_count; i++) {
193-
ei_impulse_result_bounding_box_t bb = result.visual_ad_grid_cells[i];
194-
if (bb.value == 0) {
195-
continue;
196-
}
197-
printf(" %s (%f) [ x: %u, y: %u, width: %u, height: %u ]\n", bb.label, bb.value, bb.x, bb.y, bb.width, bb.height);
198-
}
199-
printf("Visual anomaly values: Mean %.3f Max %.3f\n", result.visual_ad_result.mean_value, result.visual_ad_result.max_value);
200-
#endif
157+
// Print results, see edge-impulse-sdk/classifier/ei_print_results.h
158+
ei_print_results(&ei_default_impulse, &result);
201159

202160
// show the image on the window
203161
if (use_debug) {

source/custom.cpp

Lines changed: 6 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
#include <sstream>
3939
#include "edge-impulse-sdk/classifier/ei_run_classifier.h"
4040
#include "inc/bitmap_helper.h"
41+
#include "inc/freeform_output_helper.h"
4142

4243
std::string trim(const std::string& str) {
4344
size_t first = str.find_first_not_of(' ');
@@ -132,6 +133,9 @@ int main(int argc, char **argv) {
132133

133134
run_classifier_init();
134135

136+
// Freeform models need to reserve their own memory. Set it up (see inc/freeform_output_helper.h)
137+
freeform_outputs_init(&ei_default_impulse);
138+
135139
ei_impulse_result_t result;
136140

137141
signal_t signal;
@@ -143,64 +147,8 @@ int main(int argc, char **argv) {
143147
return 1;
144148
}
145149

146-
// print the predictions
147-
printf("Predictions (DSP: %d ms., Classification: %d ms., Anomaly: %d ms.): \n",
148-
result.timing.dsp, result.timing.classification, result.timing.anomaly);
149-
150-
#if EI_CLASSIFIER_OBJECT_TRACKING_ENABLED == 1
151-
printf("#Object tracking results:\n");
152-
for (uint32_t ix = 0; ix < result.postprocessed_output.object_tracking_output.open_traces_count; ix++) {
153-
ei_object_tracking_trace_t trace = result.postprocessed_output.object_tracking_output.open_traces[ix];
154-
printf("%s (ID %d) [ x: %u, y: %u, width: %u, height: %u ]\n", trace.label, (int)trace.id, trace.x, trace.y, trace.width, trace.height);
155-
}
156-
157-
if (result.postprocessed_output.object_tracking_output.open_traces_count == 0) {
158-
printf(" No objects found\n");
159-
}
160-
#elif EI_CLASSIFIER_OBJECT_DETECTION == 1
161-
printf("#Object detection results:\n");
162-
bool bb_found = result.bounding_boxes[0].value > 0;
163-
for (size_t ix = 0; ix < result.bounding_boxes_count; ix++) {
164-
auto bb = result.bounding_boxes[ix];
165-
if (bb.value == 0) {
166-
continue;
167-
}
168-
169-
printf("%s (%f) [ x: %u, y: %u, width: %u, height: %u ]\n", bb.label, bb.value, bb.x, bb.y, bb.width, bb.height);
170-
}
171-
172-
if (!bb_found) {
173-
printf(" No objects found\n");
174-
}
175-
176-
#elif (EI_CLASSIFIER_LABEL_COUNT == 1) && (!EI_CLASSIFIER_HAS_ANOMALY) // regression
177-
printf("#Regression results:\n");
178-
printf(" %s: ", result.classification[0].label);
179-
printf("%.5f", result.classification[0].value);
180-
printf("\n");
181-
182-
#elif EI_CLASSIFIER_LABEL_COUNT > 1 // if there is only one label, this is an anomaly only
183-
printf("#Classification results:\n");
184-
for (size_t ix = 0; ix < EI_CLASSIFIER_LABEL_COUNT; ix++) {
185-
printf(" %s: ", result.classification[ix].label);
186-
printf("%.5f", result.classification[ix].value);
187-
printf("\n");
188-
}
189-
#endif
190-
#if EI_CLASSIFIER_HAS_VISUAL_ANOMALY // visual AD
191-
printf("#Visual anomaly grid results:\n");
192-
for (uint32_t i = 0; i < result.visual_ad_count; i++) {
193-
ei_impulse_result_bounding_box_t bb = result.visual_ad_grid_cells[i];
194-
if (bb.value == 0) {
195-
continue;
196-
}
197-
198-
printf("%s (%f) [ x: %u, y: %u, width: %u, height: %u ]\n", bb.label, bb.value, bb.x, bb.y, bb.width, bb.height);
199-
}
200-
printf("Visual anomaly values: Mean %.3f Max %.3f\n", result.visual_ad_result.mean_value, result.visual_ad_result.max_value);
201-
#elif (EI_CLASSIFIER_HAS_ANOMALY > 0) // except for visual AD
202-
printf("Anomaly prediction: %.3f\n", result.anomaly);
203-
#endif
150+
// Print results, see edge-impulse-sdk/classifier/ei_print_results.h
151+
ei_print_results(&ei_default_impulse, &result);
204152

205153
create_debug_bmp(&result, &raw_features);
206154
}

0 commit comments

Comments
 (0)