Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,37 @@ set(CORE_SOURCES
core/image.h
)

set(CV_SOURCES
cv/image_gray.c
cv/image_gray.h
cv/integral.c
cv/integral.h
cv/haar.c
cv/haar.h
cv/hog.c
cv/hog.h
cv/linear_classifier.c
cv/linear_classifier.h
cv/detect.c
cv/detect.h
cv/nn.c
cv/nn.h
cv/tracker_kalman.c
cv/tracker_kalman.h
cv/tracker_template.c
cv/tracker_template.h
cv/track_hist.c
cv/track_hist.h
cv/tracker_particle.c
cv/tracker_particle.h
cv/tracker_meanshift.c
cv/tracker_meanshift.h
cv/tracker_kcf.c
cv/tracker_kcf.h
cv/track_assoc.c
cv/track_assoc.h
)

set(RUNTIME_SOURCES
runtime/runtime.c
runtime/runtime.h
Expand Down Expand Up @@ -163,6 +194,7 @@ set(DEVICE_COMMON_SOURCES
set(WRAPPER_SOURCES
wrapper/CameraWrapper.cpp
wrapper/CameraWrapper.hpp
wrapper/CvFeatureWrapper.hpp
wrapper/DisplayWrapper.cpp
wrapper/DisplayWrapper.hpp
wrapper/ImageWrapper.cpp
Expand Down Expand Up @@ -198,6 +230,7 @@ include("${EMBEDDIP_ARCH_PROFILE_FILE}")
# === Create Library Target ===
add_library(embedDIP STATIC
${CORE_SOURCES}
${CV_SOURCES}
${RUNTIME_SOURCES}
${IMGPROC_SOURCES}
${EMBEDDIP_BOARD_SOURCES}
Expand Down Expand Up @@ -228,6 +261,7 @@ endif()
target_include_directories(embedDIP PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/core>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/cv>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/imgproc>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/board>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/device/camera>
Expand Down Expand Up @@ -310,6 +344,7 @@ install(FILES

install(DIRECTORY
core/
cv/
runtime/
imgproc/
device/
Expand Down
161 changes: 161 additions & 0 deletions cv/detect.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2025 EmbedDIP

#include "cv/detect.h"

#include <stddef.h>
#include <stdint.h>

embeddip_status_t cv_detect_scan(const CvIntegralU32 *table,
const CvHaarCascade *cascade,
const CvScanConfig *scan, CvDetection *out,
size_t out_capacity, size_t *out_count)
{
int64_t max_x;
int64_t max_y;
int64_t y;

if (table == NULL || cascade == NULL || scan == NULL || out == NULL ||
out_count == NULL) {
return EMBEDDIP_ERROR_NULL_PTR;
}
if (scan->window_width == 0u || scan->window_height == 0u || scan->step_x == 0u ||
scan->step_y == 0u) {
return EMBEDDIP_ERROR_INVALID_ARG;
}
if (*out_count > out_capacity) {
return EMBEDDIP_ERROR_INVALID_SIZE;
}
/* Window larger than the table cannot be scanned; report zero new passes. */
if ((uint64_t)scan->window_width > (uint64_t)table->width ||
(uint64_t)scan->window_height > (uint64_t)table->height) {
return EMBEDDIP_OK;
}

max_x = (int64_t)table->width - (int64_t)scan->window_width;
max_y = (int64_t)table->height - (int64_t)scan->window_height;

for (y = 0; y <= max_y; y += (int64_t)scan->step_y) {
int64_t x;
for (x = 0; x <= max_x; x += (int64_t)scan->step_x) {
bool detected = false;
int32_t score = 0;
embeddip_status_t status;

status = cv_haar_cascade_score(table, (int32_t)x, (int32_t)y, cascade,
&detected, &score);
if (status != EMBEDDIP_OK) {
return status;
}
if (!detected) {
continue;
}
if (*out_count >= out_capacity) {
return EMBEDDIP_OK; /* buffer full: stop, keep what we have */
}
out[*out_count].box.x = (int32_t)x;
out[*out_count].box.y = (int32_t)y;
out[*out_count].box.width = (int32_t)scan->window_width;
out[*out_count].box.height = (int32_t)scan->window_height;
out[*out_count].score = score;
++(*out_count);
}
}

return EMBEDDIP_OK;
}

static float detect_iou(const Rectangle *a, const Rectangle *b)
{
int32_t ax1 = a->x;
int32_t ay1 = a->y;
int32_t ax2 = a->x + a->width;
int32_t ay2 = a->y + a->height;
int32_t bx1 = b->x;
int32_t by1 = b->y;
int32_t bx2 = b->x + b->width;
int32_t by2 = b->y + b->height;

int32_t ix1 = (ax1 > bx1) ? ax1 : bx1;
int32_t iy1 = (ay1 > by1) ? ay1 : by1;
int32_t ix2 = (ax2 < bx2) ? ax2 : bx2;
int32_t iy2 = (ay2 < by2) ? ay2 : by2;

int64_t iw = (int64_t)ix2 - (int64_t)ix1;
int64_t ih = (int64_t)iy2 - (int64_t)iy1;
int64_t inter;
int64_t area_a;
int64_t area_b;
int64_t uni;

if (iw <= 0 || ih <= 0) {
return 0.0f;
}
inter = iw * ih;
area_a = (int64_t)a->width * (int64_t)a->height;
area_b = (int64_t)b->width * (int64_t)b->height;
uni = area_a + area_b - inter;
if (uni <= 0) {
return 0.0f;
}
return (float)((double)inter / (double)uni);
}

embeddip_status_t cv_detect_nms(CvDetection *detections, size_t count,
float iou_threshold, size_t *out_kept)
{
size_t i;
size_t kept = 0u;

if (detections == NULL || out_kept == NULL) {
return EMBEDDIP_ERROR_NULL_PTR;
}
if (iou_threshold < 0.0f || iou_threshold > 1.0f) {
return EMBEDDIP_ERROR_INVALID_ARG;
}
if (count == 0u) {
*out_kept = 0u;
return EMBEDDIP_OK;
}

/* Stable selection sort by descending score; ties keep earlier index. */
for (i = 0u; i + 1u < count; ++i) {
size_t best = i;
size_t j;
for (j = i + 1u; j < count; ++j) {
if (detections[j].score > detections[best].score) {
best = j;
}
}
if (best != i) {
/* Rotate [i..best] right by one to preserve order of equal scores. */
CvDetection tmp = detections[best];
size_t k;
for (k = best; k > i; --k) {
detections[k] = detections[k - 1u];
}
detections[i] = tmp;
}
}

/* Greedy suppression: keep a box unless it overlaps a kept box too much. */
for (i = 0u; i < count; ++i) {
size_t s;
int suppressed = 0;
for (s = 0u; s < kept; ++s) {
if (detect_iou(&detections[i].box, &detections[s].box) > iou_threshold) {
suppressed = 1;
break;
}
}
if (!suppressed) {
if (kept != i) {
detections[kept] = detections[i];
}
++kept;
}
}

*out_kept = kept;
return EMBEDDIP_OK;
}
82 changes: 82 additions & 0 deletions cv/detect.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2025 EmbedDIP

#ifndef EMBEDDIP_CV_DETECT_H
#define EMBEDDIP_CV_DETECT_H

#include <stddef.h>
#include <stdint.h>

#include "core/error.h"
#include "core/image.h"
#include "cv/haar.h"
#include "cv/integral.h"

#ifdef __cplusplus
extern "C" {
#endif

/**
* @brief One detection: a window rectangle and its cascade score.
*/
typedef struct {
Rectangle box; /**< Detected window in integral-image pixels. */
int32_t score; /**< Cascade confidence (see cv_haar_cascade_score). */
} CvDetection;

/**
* @brief Sliding-window scan configuration.
*/
typedef struct {
uint16_t window_width; /**< Scan window width in pixels (> 0). */
uint16_t window_height; /**< Scan window height in pixels (> 0). */
uint16_t step_x; /**< Horizontal step in pixels (> 0). */
uint16_t step_y; /**< Vertical step in pixels (> 0). */
} CvScanConfig;

/**
* @brief Slide a Haar cascade across an integral image, collecting passes.
*
* Appends detections to @p out (never exceeding @p out_capacity) starting at
* @p *out_count, so the same buffer can accumulate detections from several
* scales across multiple calls. The window's own dimensions are used; the
* cascade's stored window is ignored so a caller can reuse one cascade across
* scales by scaling the integral image instead.
*
* @param[in] table Integral image to scan.
* @param[in] cascade Cascade model, read-only.
* @param[in] scan Window and step configuration.
* @param[in,out] out Caller-owned detection buffer.
* @param[in] out_capacity Capacity of @p out.
* @param[in,out] out_count In: existing count; out: count after appending.
* @return EMBEDDIP_OK on success (including a full buffer that stops early),
* error code otherwise.
*/
embeddip_status_t cv_detect_scan(const CvIntegralU32 *table,
const CvHaarCascade *cascade,
const CvScanConfig *scan, CvDetection *out,
size_t out_capacity, size_t *out_count);

/**
* @brief Greedy non-maximum suppression over detections.
*
* Sorts by descending score (stable, lower index wins ties), then keeps a
* detection only if its intersection-over-union with every already-kept
* detection is at most @p iou_threshold. Operates in place: survivors are
* moved to the front of @p detections and the survivor count is returned.
*
* @param[in,out] detections Detections to filter, reordered in place.
* @param[in] count Number of input detections.
* @param[in] iou_threshold IoU above which a lower-scored box is suppressed
* (0.0..1.0).
* @param[out] out_kept Number of survivors left at the front.
* @return EMBEDDIP_OK on success, error code otherwise.
*/
embeddip_status_t cv_detect_nms(CvDetection *detections, size_t count,
float iou_threshold, size_t *out_kept);

#ifdef __cplusplus
}
#endif

#endif /* EMBEDDIP_CV_DETECT_H */
Loading