mirror of
https://github.com/openmv/openmv.git
synced 2025-09-26 23:09:13 +08:00

Originally meant to abstract gc_collect but we could just use m_alloc and friends. Also was meant to provide functions like alloc0, alloc_maybe etc.. which are all available in MP anyway. Signed-off-by: iabdalkader <i.abdalkader@gmail.com>
1569 lines
84 KiB
C
1569 lines
84 KiB
C
/*
|
|
* SPDX-License-Identifier: MIT
|
|
*
|
|
* Copyright (C) 2013-2024 OpenMV, LLC.
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
* THE SOFTWARE.
|
|
*
|
|
* Blob detection code.
|
|
*/
|
|
#include "imlib.h"
|
|
|
|
typedef struct xylr {
|
|
int16_t x, y, l, r, t_l, b_l;
|
|
}
|
|
xylr_t;
|
|
|
|
static float sign(float x) {
|
|
return x / fabsf(x);
|
|
}
|
|
|
|
static int sum_m_to_n(int m, int n) {
|
|
return ((n * (n + 1)) - (m * (m - 1))) / 2;
|
|
}
|
|
|
|
static int sum_2_m_to_n(int m, int n) {
|
|
return ((n * (n + 1) * ((2 * n) + 1)) - (m * (m - 1) * ((2 * m) - 1))) / 6;
|
|
}
|
|
|
|
static int cumulative_moving_average(int avg, int x, int n) {
|
|
return (x + (n * avg)) / (n + 1);
|
|
}
|
|
|
|
static void bin_up(uint16_t *hist, uint16_t size, unsigned int max_size, uint16_t **new_hist, uint16_t *new_size) {
|
|
int start = -1;
|
|
|
|
for (int i = 0; i < size; i++) {
|
|
if (hist[i]) {
|
|
start = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (start != -1) {
|
|
int end = start;
|
|
|
|
for (int i = start + 1; i < size; i++) {
|
|
if (!hist[i]) {
|
|
break;
|
|
}
|
|
end = i;
|
|
}
|
|
|
|
uint16_t bin_count = end - start + 1; // >= 1
|
|
*new_size = IM_MIN(max_size, bin_count);
|
|
*new_hist = m_malloc0((*new_size) * sizeof(uint16_t));
|
|
float div_value = (*new_size) / ((float) bin_count); // Reversed so we can multiply below.
|
|
|
|
for (int i = 0; i < bin_count; i++) {
|
|
(*new_hist)[fast_floorf(i * div_value)] += hist[start + i];
|
|
}
|
|
}
|
|
}
|
|
|
|
static void merge_bins(int b_dst_start, int b_dst_end, uint16_t **b_dst_hist, uint16_t *b_dst_hist_len,
|
|
int b_src_start, int b_src_end, uint16_t **b_src_hist, uint16_t *b_src_hist_len,
|
|
unsigned int max_size) {
|
|
int start = IM_MIN(b_dst_start, b_src_start);
|
|
int end = IM_MAX(b_dst_end, b_src_end);
|
|
|
|
uint16_t bin_count = end - start + 1; // >= 1
|
|
uint16_t new_size = IM_MIN(max_size, bin_count);
|
|
uint16_t *new_hist = m_malloc0(new_size * sizeof(uint16_t));
|
|
float div_value = new_size / ((float) bin_count); // Reversed so we can multiply below.
|
|
|
|
int b_dst_bin_count = b_dst_end - b_dst_start + 1; // >= 1
|
|
uint16_t b_dst_new_size = IM_MIN((*b_dst_hist_len), b_dst_bin_count);
|
|
float b_dst_div_value = b_dst_new_size / ((float) b_dst_bin_count); // Reversed so we can multiply below.
|
|
|
|
int b_src_bin_count = b_src_end - b_src_start + 1; // >= 1
|
|
uint16_t b_src_new_size = IM_MIN((*b_src_hist_len), b_src_bin_count);
|
|
float b_src_div_value = b_src_new_size / ((float) b_src_bin_count); // Reversed so we can multiply below.
|
|
|
|
for (int i = 0; i < bin_count; i++) {
|
|
if ((b_dst_start <= (i + start)) && ((i + start) <= b_dst_end)) {
|
|
int index = fast_floorf((i + start - b_dst_start) * b_dst_div_value);
|
|
new_hist[fast_floorf(i * div_value)] += (*b_dst_hist)[index];
|
|
(*b_dst_hist)[index] = 0; // prevent from adding again...
|
|
}
|
|
if ((b_src_start <= (i + start)) && ((i + start) <= b_src_end)) {
|
|
int index = fast_floorf((i + start - b_src_start) * b_src_div_value);
|
|
new_hist[fast_floorf(i * div_value)] += (*b_src_hist)[index];
|
|
(*b_src_hist)[index] = 0; // prevent from adding again...
|
|
}
|
|
}
|
|
|
|
m_free(*b_dst_hist);
|
|
m_free(*b_src_hist);
|
|
|
|
*b_dst_hist_len = new_size;
|
|
(*b_dst_hist) = new_hist;
|
|
*b_src_hist_len = 0;
|
|
(*b_src_hist) = NULL;
|
|
}
|
|
|
|
static float calc_roundness(float blob_a, float blob_b, float blob_c) {
|
|
float roundness_div = fast_sqrtf((blob_b * blob_b) + ((blob_a - blob_c) * (blob_a - blob_c)));
|
|
float roundness_sin = IM_DIV(blob_b, roundness_div);
|
|
float roundness_cos = IM_DIV(blob_a - blob_c, roundness_div);
|
|
float roundness_add = (blob_a + blob_c) / 2;
|
|
float roundness_cos_mul = (blob_a - blob_c) / 2;
|
|
float roundness_sin_mul = blob_b / 2;
|
|
|
|
float roundness_0 = roundness_add + (roundness_cos * roundness_cos_mul) + (roundness_sin * roundness_sin_mul);
|
|
float roundness_1 = roundness_add + (roundness_cos * roundness_cos_mul) - (roundness_sin * roundness_sin_mul);
|
|
float roundness_2 = roundness_add - (roundness_cos * roundness_cos_mul) + (roundness_sin * roundness_sin_mul);
|
|
float roundness_3 = roundness_add - (roundness_cos * roundness_cos_mul) - (roundness_sin * roundness_sin_mul);
|
|
|
|
float roundness_max = IM_MAX(roundness_0, IM_MAX(roundness_1, IM_MAX(roundness_2, roundness_3)));
|
|
float roundness_min = IM_MIN(roundness_0, IM_MIN(roundness_1, IM_MIN(roundness_2, roundness_3)));
|
|
|
|
return IM_DIV(roundness_min, roundness_max);
|
|
}
|
|
|
|
void imlib_find_blobs(list_t *out, image_t *ptr, rectangle_t *roi, unsigned int x_stride, unsigned int y_stride,
|
|
list_t *thresholds, bool invert, unsigned int area_threshold, unsigned int pixels_threshold,
|
|
bool merge, int margin,
|
|
bool (*threshold_cb) (void *, find_blobs_list_lnk_data_t *), void *threshold_cb_arg,
|
|
bool (*merge_cb) (void *, find_blobs_list_lnk_data_t *, find_blobs_list_lnk_data_t *), void *merge_cb_arg,
|
|
unsigned int x_hist_bins_max, unsigned int y_hist_bins_max) {
|
|
// Same size as the image so we don't have to translate.
|
|
image_t bmp;
|
|
bmp.w = ptr->w;
|
|
bmp.h = ptr->h;
|
|
bmp.pixfmt = PIXFORMAT_BINARY;
|
|
bmp.data = fb_alloc0(image_size(&bmp), FB_ALLOC_NO_HINT);
|
|
|
|
uint16_t *x_hist_bins = NULL;
|
|
if (x_hist_bins_max) {
|
|
x_hist_bins = fb_alloc(ptr->w * sizeof(uint16_t), FB_ALLOC_NO_HINT);
|
|
}
|
|
|
|
uint16_t *y_hist_bins = NULL;
|
|
if (y_hist_bins_max) {
|
|
y_hist_bins = fb_alloc(ptr->h * sizeof(uint16_t), FB_ALLOC_NO_HINT);
|
|
}
|
|
|
|
lifo_t lifo;
|
|
size_t lifo_len;
|
|
lifo_alloc_all(&lifo, &lifo_len, sizeof(xylr_t));
|
|
|
|
list_init(out, sizeof(find_blobs_list_lnk_data_t));
|
|
|
|
size_t code = 0;
|
|
list_for_each(it, thresholds) {
|
|
color_thresholds_list_lnk_data_t *lnk_data = list_get_data(it);
|
|
|
|
switch (ptr->pixfmt) {
|
|
case PIXFORMAT_BINARY: {
|
|
for (int y = roi->y, yy = roi->y + roi->h, y_max = yy - 1; y < yy; y += y_stride) {
|
|
uint32_t *row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(ptr, y);
|
|
uint32_t *bmp_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&bmp, y);
|
|
for (int x = roi->x + (y % x_stride), xx = roi->x + roi->w, x_max = xx - 1; x < xx; x += x_stride) {
|
|
if ((!IMAGE_GET_BINARY_PIXEL_FAST(bmp_row_ptr, x))
|
|
&& COLOR_THRESHOLD_BINARY(IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x), lnk_data, invert)) {
|
|
int old_x = x;
|
|
int old_y = y;
|
|
|
|
float corners_acc[FIND_BLOBS_CORNERS_RESOLUTION];
|
|
point_t corners[FIND_BLOBS_CORNERS_RESOLUTION];
|
|
int corners_n[FIND_BLOBS_CORNERS_RESOLUTION];
|
|
// These values are initialized to their maximum before we minimize.
|
|
for (int i = 0; i < FIND_BLOBS_CORNERS_RESOLUTION; i++) {
|
|
corners[i].x =
|
|
IM_CLAMP(x_max * sign(cos_table[FIND_BLOBS_ANGLE_RESOLUTION * i]), 0, x_max);
|
|
corners[i].y =
|
|
IM_CLAMP(y_max * sign(sin_table[FIND_BLOBS_ANGLE_RESOLUTION * i]), 0, y_max);
|
|
corners_acc[i] = (corners[i].x * cos_table[FIND_BLOBS_ANGLE_RESOLUTION * i]) +
|
|
(corners[i].y * sin_table[FIND_BLOBS_ANGLE_RESOLUTION * i]);
|
|
corners_n[i] = 1;
|
|
}
|
|
|
|
int blob_pixels = 0;
|
|
int blob_perimeter = 0;
|
|
int blob_cx = 0;
|
|
int blob_cy = 0;
|
|
long long blob_a = 0;
|
|
long long blob_b = 0;
|
|
long long blob_c = 0;
|
|
|
|
if (x_hist_bins) {
|
|
memset(x_hist_bins, 0, ptr->w * sizeof(uint16_t));
|
|
}
|
|
if (y_hist_bins) {
|
|
memset(y_hist_bins, 0, ptr->h * sizeof(uint16_t));
|
|
}
|
|
|
|
// Scanline Flood Fill Algorithm //
|
|
|
|
for (;;) {
|
|
int left = x, right = x;
|
|
uint32_t *row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(ptr, y);
|
|
uint32_t *bmp_row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&bmp, y);
|
|
|
|
while ((left > roi->x)
|
|
&& (!IMAGE_GET_BINARY_PIXEL_FAST(bmp_row, left - 1))
|
|
&& COLOR_THRESHOLD_BINARY(IMAGE_GET_BINARY_PIXEL_FAST(row, left - 1), lnk_data,
|
|
invert)) {
|
|
left--;
|
|
}
|
|
|
|
while ((right < (roi->x + roi->w - 1))
|
|
&& (!IMAGE_GET_BINARY_PIXEL_FAST(bmp_row, right + 1))
|
|
&& COLOR_THRESHOLD_BINARY(IMAGE_GET_BINARY_PIXEL_FAST(row, right + 1), lnk_data,
|
|
invert)) {
|
|
right++;
|
|
}
|
|
|
|
for (int i = left; i <= right; i++) {
|
|
IMAGE_SET_BINARY_PIXEL_FAST(bmp_row, i);
|
|
}
|
|
|
|
int sum = sum_m_to_n(left, right);
|
|
int sum_2 = sum_2_m_to_n(left, right);
|
|
int cnt = right - left + 1;
|
|
int avg = sum / cnt;
|
|
|
|
for (int i = 0; i < FIND_BLOBS_CORNERS_RESOLUTION; i++) {
|
|
int x_new = (cos_table[FIND_BLOBS_ANGLE_RESOLUTION * i] > 0) ? left :
|
|
((cos_table[FIND_BLOBS_ANGLE_RESOLUTION * i] == 0) ? avg :
|
|
right);
|
|
float z = (x_new * cos_table[FIND_BLOBS_ANGLE_RESOLUTION * i]) +
|
|
(y * sin_table[FIND_BLOBS_ANGLE_RESOLUTION * i]);
|
|
if (z < corners_acc[i]) {
|
|
corners_acc[i] = z;
|
|
corners[i].x = x_new;
|
|
corners[i].y = y;
|
|
corners_n[i] = 1;
|
|
} else if (z == corners_acc[i]) {
|
|
corners[i].x = cumulative_moving_average(corners[i].x, x_new, corners_n[i]);
|
|
corners[i].y = cumulative_moving_average(corners[i].y, y, corners_n[i]);
|
|
corners_n[i] += 1;
|
|
}
|
|
}
|
|
|
|
blob_pixels += cnt;
|
|
blob_perimeter += 2;
|
|
blob_cx += sum;
|
|
blob_cy += y * cnt;
|
|
blob_a += sum_2;
|
|
blob_b += y * sum;
|
|
blob_c += y * y * cnt;
|
|
|
|
if (y_hist_bins) {
|
|
y_hist_bins[y] += cnt;
|
|
}
|
|
if (x_hist_bins) {
|
|
for (int i = left; i <= right; i++) {
|
|
x_hist_bins[i] += 1;
|
|
}
|
|
}
|
|
|
|
int top_left = left;
|
|
int bot_left = left;
|
|
bool break_out = false;
|
|
for (;;) {
|
|
if (lifo_size(&lifo) < lifo_len) {
|
|
|
|
if (y > roi->y) {
|
|
row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(ptr, y - 1);
|
|
bmp_row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&bmp, y - 1);
|
|
|
|
bool recurse = false;
|
|
for (int i = top_left; i <= right; i++) {
|
|
bool ok = true; // Does nothing if thresholding is skipped.
|
|
|
|
if ((!IMAGE_GET_BINARY_PIXEL_FAST(bmp_row, i))
|
|
&& (ok =
|
|
COLOR_THRESHOLD_BINARY(IMAGE_GET_BINARY_PIXEL_FAST(row, i),
|
|
lnk_data,
|
|
invert))) {
|
|
xylr_t context;
|
|
context.x = x;
|
|
context.y = y;
|
|
context.l = left;
|
|
context.r = right;
|
|
context.t_l = i + 1; // Don't test the same pixel again...
|
|
context.b_l = bot_left;
|
|
lifo_enqueue(&lifo, &context);
|
|
x = i;
|
|
y = y - 1;
|
|
recurse = true;
|
|
break;
|
|
}
|
|
|
|
blob_perimeter += (!ok) && (i != left) && (i != right);
|
|
}
|
|
if (recurse) {
|
|
break;
|
|
}
|
|
} else {
|
|
blob_perimeter += right - left + 1;
|
|
}
|
|
|
|
if (y < (roi->y + roi->h - 1)) {
|
|
row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(ptr, y + 1);
|
|
bmp_row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&bmp, y + 1);
|
|
|
|
bool recurse = false;
|
|
for (int i = bot_left; i <= right; i++) {
|
|
bool ok = true; // Does nothing if thresholding is skipped.
|
|
|
|
if ((!IMAGE_GET_BINARY_PIXEL_FAST(bmp_row, i))
|
|
&& (ok =
|
|
COLOR_THRESHOLD_BINARY(IMAGE_GET_BINARY_PIXEL_FAST(row, i),
|
|
lnk_data,
|
|
invert))) {
|
|
xylr_t context;
|
|
context.x = x;
|
|
context.y = y;
|
|
context.l = left;
|
|
context.r = right;
|
|
context.t_l = top_left;
|
|
context.b_l = i + 1; // Don't test the same pixel again...
|
|
lifo_enqueue(&lifo, &context);
|
|
x = i;
|
|
y = y + 1;
|
|
recurse = true;
|
|
break;
|
|
}
|
|
|
|
blob_perimeter += (!ok) && (i != left) && (i != right);
|
|
}
|
|
if (recurse) {
|
|
break;
|
|
}
|
|
} else {
|
|
blob_perimeter += right - left + 1;
|
|
}
|
|
} else {
|
|
blob_perimeter += (right - left + 1) * 2;
|
|
}
|
|
|
|
if (!lifo_size(&lifo)) {
|
|
break_out = true;
|
|
break;
|
|
}
|
|
|
|
xylr_t context;
|
|
lifo_dequeue(&lifo, &context);
|
|
x = context.x;
|
|
y = context.y;
|
|
left = context.l;
|
|
right = context.r;
|
|
top_left = context.t_l;
|
|
bot_left = context.b_l;
|
|
}
|
|
|
|
if (break_out) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
rectangle_t rect;
|
|
rect.x = corners[(FIND_BLOBS_CORNERS_RESOLUTION * 0) / 4].x; // l
|
|
rect.y = corners[(FIND_BLOBS_CORNERS_RESOLUTION * 1) / 4].y; // t
|
|
rect.w = corners[(FIND_BLOBS_CORNERS_RESOLUTION * 2) / 4].x -
|
|
corners[(FIND_BLOBS_CORNERS_RESOLUTION * 0) / 4].x + 1; // r - l + 1
|
|
rect.h = corners[(FIND_BLOBS_CORNERS_RESOLUTION * 3) / 4].y -
|
|
corners[(FIND_BLOBS_CORNERS_RESOLUTION * 1) / 4].y + 1; // b - t + 1
|
|
|
|
if (((rect.w * rect.h) >= area_threshold) && (blob_pixels >= pixels_threshold)) {
|
|
|
|
// http://www.cse.usf.edu/~r1k/MachineVisionBook/MachineVision.files/MachineVision_Chapter2.pdf
|
|
// https://www.strchr.com/standard_deviation_in_one_pass
|
|
//
|
|
// a = sigma(x*x) + (mx*sigma(x)) + (mx*sigma(x)) + (sigma()*mx*mx)
|
|
// b = sigma(x*y) + (mx*sigma(y)) + (my*sigma(x)) + (sigma()*mx*my)
|
|
// c = sigma(y*y) + (my*sigma(y)) + (my*sigma(y)) + (sigma()*my*my)
|
|
//
|
|
// blob_a = sigma(x*x)
|
|
// blob_b = sigma(x*y)
|
|
// blob_c = sigma(y*y)
|
|
// blob_cx = sigma(x)
|
|
// blob_cy = sigma(y)
|
|
// blob_pixels = sigma()
|
|
|
|
float b_mx = blob_cx / ((float) blob_pixels);
|
|
float b_my = blob_cy / ((float) blob_pixels);
|
|
int mx = fast_roundf(b_mx); // x centroid
|
|
int my = fast_roundf(b_my); // y centroid
|
|
int small_blob_a = blob_a - ((mx * blob_cx) + (mx * blob_cx)) + (blob_pixels * mx * mx);
|
|
int small_blob_b = blob_b - ((mx * blob_cy) + (my * blob_cx)) + (blob_pixels * mx * my);
|
|
int small_blob_c = blob_c - ((my * blob_cy) + (my * blob_cy)) + (blob_pixels * my * my);
|
|
|
|
find_blobs_list_lnk_data_t lnk_blob;
|
|
memcpy(lnk_blob.corners, corners, FIND_BLOBS_CORNERS_RESOLUTION * sizeof(point_t));
|
|
memcpy(&lnk_blob.rect, &rect, sizeof(rectangle_t));
|
|
lnk_blob.pixels = blob_pixels;
|
|
lnk_blob.perimeter = blob_perimeter;
|
|
lnk_blob.code = 1 << code;
|
|
lnk_blob.count = 1;
|
|
lnk_blob.centroid_x = b_mx;
|
|
lnk_blob.centroid_y = b_my;
|
|
lnk_blob.rotation =
|
|
(small_blob_a !=
|
|
small_blob_c) ? (fast_atan2f(2 * small_blob_b, small_blob_a - small_blob_c) / 2.0f) : 0.0f;
|
|
lnk_blob.roundness = calc_roundness(small_blob_a, small_blob_b, small_blob_c);
|
|
lnk_blob.x_hist_bins_count = 0;
|
|
lnk_blob.x_hist_bins = NULL;
|
|
lnk_blob.y_hist_bins_count = 0;
|
|
lnk_blob.y_hist_bins = NULL;
|
|
// These store the current average accumulation.
|
|
lnk_blob.centroid_x_acc = lnk_blob.centroid_x * lnk_blob.pixels;
|
|
lnk_blob.centroid_y_acc = lnk_blob.centroid_y * lnk_blob.pixels;
|
|
lnk_blob.rotation_acc_x = cosf(lnk_blob.rotation) * lnk_blob.pixels;
|
|
lnk_blob.rotation_acc_y = sinf(lnk_blob.rotation) * lnk_blob.pixels;
|
|
lnk_blob.roundness_acc = lnk_blob.roundness * lnk_blob.pixels;
|
|
|
|
if (x_hist_bins) {
|
|
bin_up(x_hist_bins,
|
|
ptr->w,
|
|
x_hist_bins_max,
|
|
&lnk_blob.x_hist_bins,
|
|
&lnk_blob.x_hist_bins_count);
|
|
}
|
|
|
|
if (y_hist_bins) {
|
|
bin_up(y_hist_bins,
|
|
ptr->h,
|
|
y_hist_bins_max,
|
|
&lnk_blob.y_hist_bins,
|
|
&lnk_blob.y_hist_bins_count);
|
|
}
|
|
|
|
bool add_to_list = threshold_cb_arg == NULL;
|
|
if (!add_to_list) {
|
|
// Protect ourselves from caught exceptions in the callback
|
|
// code from freeing our fb_alloc() stack.
|
|
fb_alloc_mark();
|
|
fb_alloc_mark_permanent();
|
|
add_to_list = threshold_cb(threshold_cb_arg, &lnk_blob);
|
|
fb_alloc_free_till_mark_past_mark_permanent();
|
|
}
|
|
|
|
if (add_to_list) {
|
|
list_push_back(out, &lnk_blob);
|
|
} else {
|
|
if (lnk_blob.x_hist_bins) {
|
|
m_free(lnk_blob.x_hist_bins);
|
|
}
|
|
if (lnk_blob.y_hist_bins) {
|
|
m_free(lnk_blob.y_hist_bins);
|
|
}
|
|
}
|
|
}
|
|
|
|
x = old_x;
|
|
y = old_y;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case PIXFORMAT_GRAYSCALE: {
|
|
for (int y = roi->y, yy = roi->y + roi->h, y_max = yy - 1; y < yy; y += y_stride) {
|
|
uint8_t *row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(ptr, y);
|
|
uint32_t *bmp_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&bmp, y);
|
|
for (int x = roi->x + (y % x_stride), xx = roi->x + roi->w, x_max = xx - 1; x < xx; x += x_stride) {
|
|
if ((!IMAGE_GET_BINARY_PIXEL_FAST(bmp_row_ptr, x))
|
|
&& COLOR_THRESHOLD_GRAYSCALE(IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, x), lnk_data, invert)) {
|
|
int old_x = x;
|
|
int old_y = y;
|
|
|
|
float corners_acc[FIND_BLOBS_CORNERS_RESOLUTION];
|
|
point_t corners[FIND_BLOBS_CORNERS_RESOLUTION];
|
|
int corners_n[FIND_BLOBS_CORNERS_RESOLUTION];
|
|
// These values are initialized to their maximum before we minimize.
|
|
for (int i = 0; i < FIND_BLOBS_CORNERS_RESOLUTION; i++) {
|
|
corners[i].x =
|
|
IM_CLAMP(x_max * sign(cos_table[FIND_BLOBS_ANGLE_RESOLUTION * i]), 0, x_max);
|
|
corners[i].y =
|
|
IM_CLAMP(y_max * sign(sin_table[FIND_BLOBS_ANGLE_RESOLUTION * i]), 0, y_max);
|
|
corners_acc[i] = (corners[i].x * cos_table[FIND_BLOBS_ANGLE_RESOLUTION * i]) +
|
|
(corners[i].y * sin_table[FIND_BLOBS_ANGLE_RESOLUTION * i]);
|
|
corners_n[i] = 1;
|
|
}
|
|
|
|
int blob_pixels = 0;
|
|
int blob_perimeter = 0;
|
|
int blob_cx = 0;
|
|
int blob_cy = 0;
|
|
long long blob_a = 0;
|
|
long long blob_b = 0;
|
|
long long blob_c = 0;
|
|
|
|
if (x_hist_bins) {
|
|
memset(x_hist_bins, 0, ptr->w * sizeof(uint16_t));
|
|
}
|
|
if (y_hist_bins) {
|
|
memset(y_hist_bins, 0, ptr->h * sizeof(uint16_t));
|
|
}
|
|
|
|
// Scanline Flood Fill Algorithm //
|
|
|
|
for (;;) {
|
|
int left = x, right = x;
|
|
uint8_t *row = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(ptr, y);
|
|
uint32_t *bmp_row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&bmp, y);
|
|
|
|
while ((left > roi->x)
|
|
&& (!IMAGE_GET_BINARY_PIXEL_FAST(bmp_row, left - 1))
|
|
&& COLOR_THRESHOLD_GRAYSCALE(IMAGE_GET_GRAYSCALE_PIXEL_FAST(row, left - 1), lnk_data,
|
|
invert)) {
|
|
left--;
|
|
}
|
|
|
|
while ((right < (roi->x + roi->w - 1))
|
|
&& (!IMAGE_GET_BINARY_PIXEL_FAST(bmp_row, right + 1))
|
|
&& COLOR_THRESHOLD_GRAYSCALE(IMAGE_GET_GRAYSCALE_PIXEL_FAST(row, right + 1), lnk_data,
|
|
invert)) {
|
|
right++;
|
|
}
|
|
|
|
for (int i = left; i <= right; i++) {
|
|
IMAGE_SET_BINARY_PIXEL_FAST(bmp_row, i);
|
|
}
|
|
|
|
int sum = sum_m_to_n(left, right);
|
|
int sum_2 = sum_2_m_to_n(left, right);
|
|
int cnt = right - left + 1;
|
|
int avg = sum / cnt;
|
|
|
|
for (int i = 0; i < FIND_BLOBS_CORNERS_RESOLUTION; i++) {
|
|
int x_new = (cos_table[FIND_BLOBS_ANGLE_RESOLUTION * i] > 0) ? left :
|
|
((cos_table[FIND_BLOBS_ANGLE_RESOLUTION * i] == 0) ? avg :
|
|
right);
|
|
float z = (x_new * cos_table[FIND_BLOBS_ANGLE_RESOLUTION * i]) +
|
|
(y * sin_table[FIND_BLOBS_ANGLE_RESOLUTION * i]);
|
|
if (z < corners_acc[i]) {
|
|
corners_acc[i] = z;
|
|
corners[i].x = x_new;
|
|
corners[i].y = y;
|
|
corners_n[i] = 1;
|
|
} else if (z == corners_acc[i]) {
|
|
corners[i].x = cumulative_moving_average(corners[i].x, x_new, corners_n[i]);
|
|
corners[i].y = cumulative_moving_average(corners[i].y, y, corners_n[i]);
|
|
corners_n[i] += 1;
|
|
}
|
|
}
|
|
|
|
blob_pixels += cnt;
|
|
blob_perimeter += 2;
|
|
blob_cx += sum;
|
|
blob_cy += y * cnt;
|
|
blob_a += sum_2;
|
|
blob_b += y * sum;
|
|
blob_c += y * y * cnt;
|
|
|
|
if (y_hist_bins) {
|
|
y_hist_bins[y] += cnt;
|
|
}
|
|
if (x_hist_bins) {
|
|
for (int i = left; i <= right; i++) {
|
|
x_hist_bins[i] += 1;
|
|
}
|
|
}
|
|
|
|
int top_left = left;
|
|
int bot_left = left;
|
|
bool break_out = false;
|
|
for (;;) {
|
|
if (lifo_size(&lifo) < lifo_len) {
|
|
|
|
if (y > roi->y) {
|
|
row = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(ptr, y - 1);
|
|
bmp_row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&bmp, y - 1);
|
|
|
|
bool recurse = false;
|
|
for (int i = top_left; i <= right; i++) {
|
|
bool ok = true; // Does nothing if thresholding is skipped.
|
|
|
|
if ((!IMAGE_GET_BINARY_PIXEL_FAST(bmp_row, i))
|
|
&& (ok =
|
|
COLOR_THRESHOLD_GRAYSCALE(IMAGE_GET_GRAYSCALE_PIXEL_FAST(row, i),
|
|
lnk_data,
|
|
invert))) {
|
|
xylr_t context;
|
|
context.x = x;
|
|
context.y = y;
|
|
context.l = left;
|
|
context.r = right;
|
|
context.t_l = i + 1; // Don't test the same pixel again...
|
|
context.b_l = bot_left;
|
|
lifo_enqueue(&lifo, &context);
|
|
x = i;
|
|
y = y - 1;
|
|
recurse = true;
|
|
break;
|
|
}
|
|
|
|
blob_perimeter += (!ok) && (i != left) && (i != right);
|
|
}
|
|
if (recurse) {
|
|
break;
|
|
}
|
|
} else {
|
|
blob_perimeter += right - left + 1;
|
|
}
|
|
|
|
if (y < (roi->y + roi->h - 1)) {
|
|
row = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(ptr, y + 1);
|
|
bmp_row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&bmp, y + 1);
|
|
|
|
bool recurse = false;
|
|
for (int i = bot_left; i <= right; i++) {
|
|
bool ok = true; // Does nothing if thresholding is skipped.
|
|
|
|
if ((!IMAGE_GET_BINARY_PIXEL_FAST(bmp_row, i))
|
|
&& (ok =
|
|
COLOR_THRESHOLD_GRAYSCALE(IMAGE_GET_GRAYSCALE_PIXEL_FAST(row, i),
|
|
lnk_data,
|
|
invert))) {
|
|
xylr_t context;
|
|
context.x = x;
|
|
context.y = y;
|
|
context.l = left;
|
|
context.r = right;
|
|
context.t_l = top_left;
|
|
context.b_l = i + 1; // Don't test the same pixel again...
|
|
lifo_enqueue(&lifo, &context);
|
|
x = i;
|
|
y = y + 1;
|
|
recurse = true;
|
|
break;
|
|
}
|
|
|
|
blob_perimeter += (!ok) && (i != left) && (i != right);
|
|
}
|
|
if (recurse) {
|
|
break;
|
|
}
|
|
} else {
|
|
blob_perimeter += right - left + 1;
|
|
}
|
|
} else {
|
|
blob_perimeter += (right - left + 1) * 2;
|
|
}
|
|
|
|
if (!lifo_size(&lifo)) {
|
|
break_out = true;
|
|
break;
|
|
}
|
|
|
|
xylr_t context;
|
|
lifo_dequeue(&lifo, &context);
|
|
x = context.x;
|
|
y = context.y;
|
|
left = context.l;
|
|
right = context.r;
|
|
top_left = context.t_l;
|
|
bot_left = context.b_l;
|
|
}
|
|
|
|
if (break_out) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
rectangle_t rect;
|
|
rect.x = corners[(FIND_BLOBS_CORNERS_RESOLUTION * 0) / 4].x; // l
|
|
rect.y = corners[(FIND_BLOBS_CORNERS_RESOLUTION * 1) / 4].y; // t
|
|
rect.w = corners[(FIND_BLOBS_CORNERS_RESOLUTION * 2) / 4].x -
|
|
corners[(FIND_BLOBS_CORNERS_RESOLUTION * 0) / 4].x + 1; // r - l + 1
|
|
rect.h = corners[(FIND_BLOBS_CORNERS_RESOLUTION * 3) / 4].y -
|
|
corners[(FIND_BLOBS_CORNERS_RESOLUTION * 1) / 4].y + 1; // b - t + 1
|
|
|
|
if (((rect.w * rect.h) >= area_threshold) && (blob_pixels >= pixels_threshold)) {
|
|
|
|
// http://www.cse.usf.edu/~r1k/MachineVisionBook/MachineVision.files/MachineVision_Chapter2.pdf
|
|
// https://www.strchr.com/standard_deviation_in_one_pass
|
|
//
|
|
// a = sigma(x*x) + (mx*sigma(x)) + (mx*sigma(x)) + (sigma()*mx*mx)
|
|
// b = sigma(x*y) + (mx*sigma(y)) + (my*sigma(x)) + (sigma()*mx*my)
|
|
// c = sigma(y*y) + (my*sigma(y)) + (my*sigma(y)) + (sigma()*my*my)
|
|
//
|
|
// blob_a = sigma(x*x)
|
|
// blob_b = sigma(x*y)
|
|
// blob_c = sigma(y*y)
|
|
// blob_cx = sigma(x)
|
|
// blob_cy = sigma(y)
|
|
// blob_pixels = sigma()
|
|
|
|
float b_mx = blob_cx / ((float) blob_pixels);
|
|
float b_my = blob_cy / ((float) blob_pixels);
|
|
int mx = fast_roundf(b_mx); // x centroid
|
|
int my = fast_roundf(b_my); // y centroid
|
|
int small_blob_a = blob_a - ((mx * blob_cx) + (mx * blob_cx)) + (blob_pixels * mx * mx);
|
|
int small_blob_b = blob_b - ((mx * blob_cy) + (my * blob_cx)) + (blob_pixels * mx * my);
|
|
int small_blob_c = blob_c - ((my * blob_cy) + (my * blob_cy)) + (blob_pixels * my * my);
|
|
|
|
find_blobs_list_lnk_data_t lnk_blob;
|
|
memcpy(lnk_blob.corners, corners, FIND_BLOBS_CORNERS_RESOLUTION * sizeof(point_t));
|
|
memcpy(&lnk_blob.rect, &rect, sizeof(rectangle_t));
|
|
lnk_blob.pixels = blob_pixels;
|
|
lnk_blob.perimeter = blob_perimeter;
|
|
lnk_blob.code = 1 << code;
|
|
lnk_blob.count = 1;
|
|
lnk_blob.centroid_x = b_mx;
|
|
lnk_blob.centroid_y = b_my;
|
|
lnk_blob.rotation =
|
|
(small_blob_a !=
|
|
small_blob_c) ? (fast_atan2f(2 * small_blob_b, small_blob_a - small_blob_c) / 2.0f) : 0.0f;
|
|
lnk_blob.roundness = calc_roundness(small_blob_a, small_blob_b, small_blob_c);
|
|
lnk_blob.x_hist_bins_count = 0;
|
|
lnk_blob.x_hist_bins = NULL;
|
|
lnk_blob.y_hist_bins_count = 0;
|
|
lnk_blob.y_hist_bins = NULL;
|
|
// These store the current average accumulation.
|
|
lnk_blob.centroid_x_acc = lnk_blob.centroid_x * lnk_blob.pixels;
|
|
lnk_blob.centroid_y_acc = lnk_blob.centroid_y * lnk_blob.pixels;
|
|
lnk_blob.rotation_acc_x = cosf(lnk_blob.rotation) * lnk_blob.pixels;
|
|
lnk_blob.rotation_acc_y = sinf(lnk_blob.rotation) * lnk_blob.pixels;
|
|
lnk_blob.roundness_acc = lnk_blob.roundness * lnk_blob.pixels;
|
|
|
|
if (x_hist_bins) {
|
|
bin_up(x_hist_bins,
|
|
ptr->w,
|
|
x_hist_bins_max,
|
|
&lnk_blob.x_hist_bins,
|
|
&lnk_blob.x_hist_bins_count);
|
|
}
|
|
|
|
if (y_hist_bins) {
|
|
bin_up(y_hist_bins,
|
|
ptr->h,
|
|
y_hist_bins_max,
|
|
&lnk_blob.y_hist_bins,
|
|
&lnk_blob.y_hist_bins_count);
|
|
}
|
|
|
|
bool add_to_list = threshold_cb_arg == NULL;
|
|
if (!add_to_list) {
|
|
// Protect ourselves from caught exceptions in the callback
|
|
// code from freeing our fb_alloc() stack.
|
|
fb_alloc_mark();
|
|
fb_alloc_mark_permanent();
|
|
add_to_list = threshold_cb(threshold_cb_arg, &lnk_blob);
|
|
fb_alloc_free_till_mark_past_mark_permanent();
|
|
}
|
|
|
|
if (add_to_list) {
|
|
list_push_back(out, &lnk_blob);
|
|
} else {
|
|
if (lnk_blob.x_hist_bins) {
|
|
m_free(lnk_blob.x_hist_bins);
|
|
}
|
|
if (lnk_blob.y_hist_bins) {
|
|
m_free(lnk_blob.y_hist_bins);
|
|
}
|
|
}
|
|
}
|
|
|
|
x = old_x;
|
|
y = old_y;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case PIXFORMAT_RGB565: {
|
|
for (int y = roi->y, yy = roi->y + roi->h, y_max = yy - 1; y < yy; y += y_stride) {
|
|
uint16_t *row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(ptr, y);
|
|
uint32_t *bmp_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&bmp, y);
|
|
for (int x = roi->x + (y % x_stride), xx = roi->x + roi->w, x_max = xx - 1; x < xx; x += x_stride) {
|
|
if ((!IMAGE_GET_BINARY_PIXEL_FAST(bmp_row_ptr, x))
|
|
&& COLOR_THRESHOLD_RGB565(IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x), lnk_data, invert)) {
|
|
int old_x = x;
|
|
int old_y = y;
|
|
|
|
float corners_acc[FIND_BLOBS_CORNERS_RESOLUTION];
|
|
point_t corners[FIND_BLOBS_CORNERS_RESOLUTION];
|
|
int corners_n[FIND_BLOBS_CORNERS_RESOLUTION];
|
|
// Ensures that maximum goes all the way to the edge of the image.
|
|
for (int i = 0; i < FIND_BLOBS_CORNERS_RESOLUTION; i++) {
|
|
corners[i].x =
|
|
IM_CLAMP(x_max * sign(cos_table[FIND_BLOBS_ANGLE_RESOLUTION * i]), 0, x_max);
|
|
corners[i].y =
|
|
IM_CLAMP(y_max * sign(sin_table[FIND_BLOBS_ANGLE_RESOLUTION * i]), 0, y_max);
|
|
corners_acc[i] = (corners[i].x * cos_table[FIND_BLOBS_ANGLE_RESOLUTION * i]) +
|
|
(corners[i].y * sin_table[FIND_BLOBS_ANGLE_RESOLUTION * i]);
|
|
corners_n[i] = 1;
|
|
}
|
|
|
|
int blob_pixels = 0;
|
|
int blob_perimeter = 0;
|
|
int blob_cx = 0;
|
|
int blob_cy = 0;
|
|
long long blob_a = 0;
|
|
long long blob_b = 0;
|
|
long long blob_c = 0;
|
|
|
|
if (x_hist_bins) {
|
|
memset(x_hist_bins, 0, ptr->w * sizeof(uint16_t));
|
|
}
|
|
if (y_hist_bins) {
|
|
memset(y_hist_bins, 0, ptr->h * sizeof(uint16_t));
|
|
}
|
|
|
|
// Scanline Flood Fill Algorithm //
|
|
|
|
for (;;) {
|
|
int left = x, right = x;
|
|
uint16_t *row = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(ptr, y);
|
|
uint32_t *bmp_row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&bmp, y);
|
|
|
|
while ((left > roi->x)
|
|
&& (!IMAGE_GET_BINARY_PIXEL_FAST(bmp_row, left - 1))
|
|
&& COLOR_THRESHOLD_RGB565(IMAGE_GET_RGB565_PIXEL_FAST(row, left - 1), lnk_data,
|
|
invert)) {
|
|
left--;
|
|
}
|
|
|
|
while ((right < (roi->x + roi->w - 1))
|
|
&& (!IMAGE_GET_BINARY_PIXEL_FAST(bmp_row, right + 1))
|
|
&& COLOR_THRESHOLD_RGB565(IMAGE_GET_RGB565_PIXEL_FAST(row, right + 1), lnk_data,
|
|
invert)) {
|
|
right++;
|
|
}
|
|
|
|
for (int i = left; i <= right; i++) {
|
|
IMAGE_SET_BINARY_PIXEL_FAST(bmp_row, i);
|
|
}
|
|
|
|
int sum = sum_m_to_n(left, right);
|
|
int sum_2 = sum_2_m_to_n(left, right);
|
|
int cnt = right - left + 1;
|
|
int avg = sum / cnt;
|
|
|
|
for (int i = 0; i < FIND_BLOBS_CORNERS_RESOLUTION; i++) {
|
|
int x_new = (cos_table[FIND_BLOBS_ANGLE_RESOLUTION * i] > 0) ? left :
|
|
((cos_table[FIND_BLOBS_ANGLE_RESOLUTION * i] == 0) ? avg :
|
|
right);
|
|
float z = (x_new * cos_table[FIND_BLOBS_ANGLE_RESOLUTION * i]) +
|
|
(y * sin_table[FIND_BLOBS_ANGLE_RESOLUTION * i]);
|
|
if (z < corners_acc[i]) {
|
|
corners_acc[i] = z;
|
|
corners[i].x = x_new;
|
|
corners[i].y = y;
|
|
corners_n[i] = 1;
|
|
} else if (z == corners_acc[i]) {
|
|
corners[i].x = cumulative_moving_average(corners[i].x, x_new, corners_n[i]);
|
|
corners[i].y = cumulative_moving_average(corners[i].y, y, corners_n[i]);
|
|
corners_n[i] += 1;
|
|
}
|
|
}
|
|
|
|
blob_pixels += cnt;
|
|
blob_perimeter += 2;
|
|
blob_cx += sum;
|
|
blob_cy += y * cnt;
|
|
blob_a += sum_2;
|
|
blob_b += y * sum;
|
|
blob_c += y * y * cnt;
|
|
|
|
if (y_hist_bins) {
|
|
y_hist_bins[y] += cnt;
|
|
}
|
|
if (x_hist_bins) {
|
|
for (int i = left; i <= right; i++) {
|
|
x_hist_bins[i] += 1;
|
|
}
|
|
}
|
|
|
|
int top_left = left;
|
|
int bot_left = left;
|
|
bool break_out = false;
|
|
for (;;) {
|
|
if (lifo_size(&lifo) < lifo_len) {
|
|
|
|
if (y > roi->y) {
|
|
row = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(ptr, y - 1);
|
|
bmp_row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&bmp, y - 1);
|
|
|
|
bool recurse = false;
|
|
for (int i = top_left; i <= right; i++) {
|
|
bool ok = true; // Does nothing if thresholding is skipped.
|
|
|
|
if ((!IMAGE_GET_BINARY_PIXEL_FAST(bmp_row, i))
|
|
&& (ok =
|
|
COLOR_THRESHOLD_RGB565(IMAGE_GET_RGB565_PIXEL_FAST(row, i),
|
|
lnk_data,
|
|
invert))) {
|
|
xylr_t context;
|
|
context.x = x;
|
|
context.y = y;
|
|
context.l = left;
|
|
context.r = right;
|
|
context.t_l = i + 1; // Don't test the same pixel again...
|
|
context.b_l = bot_left;
|
|
lifo_enqueue(&lifo, &context);
|
|
x = i;
|
|
y = y - 1;
|
|
recurse = true;
|
|
break;
|
|
}
|
|
|
|
blob_perimeter += (!ok) && (i != left) && (i != right);
|
|
}
|
|
if (recurse) {
|
|
break;
|
|
}
|
|
} else {
|
|
blob_perimeter += right - left + 1;
|
|
}
|
|
|
|
if (y < (roi->y + roi->h - 1)) {
|
|
row = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(ptr, y + 1);
|
|
bmp_row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&bmp, y + 1);
|
|
|
|
bool recurse = false;
|
|
for (int i = bot_left; i <= right; i++) {
|
|
bool ok = true; // Does nothing if thresholding is skipped.
|
|
|
|
if ((!IMAGE_GET_BINARY_PIXEL_FAST(bmp_row, i))
|
|
&& (ok =
|
|
COLOR_THRESHOLD_RGB565(IMAGE_GET_RGB565_PIXEL_FAST(row, i),
|
|
lnk_data,
|
|
invert))) {
|
|
xylr_t context;
|
|
context.x = x;
|
|
context.y = y;
|
|
context.l = left;
|
|
context.r = right;
|
|
context.t_l = top_left;
|
|
context.b_l = i + 1; // Don't test the same pixel again...
|
|
lifo_enqueue(&lifo, &context);
|
|
x = i;
|
|
y = y + 1;
|
|
recurse = true;
|
|
break;
|
|
}
|
|
|
|
blob_perimeter += (!ok) && (i != left) && (i != right);
|
|
}
|
|
if (recurse) {
|
|
break;
|
|
}
|
|
} else {
|
|
blob_perimeter += right - left + 1;
|
|
}
|
|
} else {
|
|
blob_perimeter += (right - left + 1) * 2;
|
|
}
|
|
|
|
if (!lifo_size(&lifo)) {
|
|
break_out = true;
|
|
break;
|
|
}
|
|
|
|
xylr_t context;
|
|
lifo_dequeue(&lifo, &context);
|
|
x = context.x;
|
|
y = context.y;
|
|
left = context.l;
|
|
right = context.r;
|
|
top_left = context.t_l;
|
|
bot_left = context.b_l;
|
|
}
|
|
|
|
if (break_out) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
rectangle_t rect;
|
|
rect.x = corners[(FIND_BLOBS_CORNERS_RESOLUTION * 0) / 4].x; // l
|
|
rect.y = corners[(FIND_BLOBS_CORNERS_RESOLUTION * 1) / 4].y; // t
|
|
rect.w = corners[(FIND_BLOBS_CORNERS_RESOLUTION * 2) / 4].x -
|
|
corners[(FIND_BLOBS_CORNERS_RESOLUTION * 0) / 4].x + 1; // r - l + 1
|
|
rect.h = corners[(FIND_BLOBS_CORNERS_RESOLUTION * 3) / 4].y -
|
|
corners[(FIND_BLOBS_CORNERS_RESOLUTION * 1) / 4].y + 1; // b - t + 1
|
|
|
|
if (((rect.w * rect.h) >= area_threshold) && (blob_pixels >= pixels_threshold)) {
|
|
|
|
// http://www.cse.usf.edu/~r1k/MachineVisionBook/MachineVision.files/MachineVision_Chapter2.pdf
|
|
// https://www.strchr.com/standard_deviation_in_one_pass
|
|
//
|
|
// a = sigma(x*x) + (mx*sigma(x)) + (mx*sigma(x)) + (sigma()*mx*mx)
|
|
// b = sigma(x*y) + (mx*sigma(y)) + (my*sigma(x)) + (sigma()*mx*my)
|
|
// c = sigma(y*y) + (my*sigma(y)) + (my*sigma(y)) + (sigma()*my*my)
|
|
//
|
|
// blob_a = sigma(x*x)
|
|
// blob_b = sigma(x*y)
|
|
// blob_c = sigma(y*y)
|
|
// blob_cx = sigma(x)
|
|
// blob_cy = sigma(y)
|
|
// blob_pixels = sigma()
|
|
|
|
float b_mx = blob_cx / ((float) blob_pixels);
|
|
float b_my = blob_cy / ((float) blob_pixels);
|
|
int mx = fast_roundf(b_mx); // x centroid
|
|
int my = fast_roundf(b_my); // y centroid
|
|
int small_blob_a = blob_a - ((mx * blob_cx) + (mx * blob_cx)) + (blob_pixels * mx * mx);
|
|
int small_blob_b = blob_b - ((mx * blob_cy) + (my * blob_cx)) + (blob_pixels * mx * my);
|
|
int small_blob_c = blob_c - ((my * blob_cy) + (my * blob_cy)) + (blob_pixels * my * my);
|
|
|
|
find_blobs_list_lnk_data_t lnk_blob;
|
|
memcpy(lnk_blob.corners, corners, FIND_BLOBS_CORNERS_RESOLUTION * sizeof(point_t));
|
|
memcpy(&lnk_blob.rect, &rect, sizeof(rectangle_t));
|
|
lnk_blob.pixels = blob_pixels;
|
|
lnk_blob.perimeter = blob_perimeter;
|
|
lnk_blob.code = 1 << code;
|
|
lnk_blob.count = 1;
|
|
lnk_blob.centroid_x = b_mx;
|
|
lnk_blob.centroid_y = b_my;
|
|
lnk_blob.rotation =
|
|
(small_blob_a !=
|
|
small_blob_c) ? (fast_atan2f(2 * small_blob_b, small_blob_a - small_blob_c) / 2.0f) : 0.0f;
|
|
lnk_blob.roundness = calc_roundness(small_blob_a, small_blob_b, small_blob_c);
|
|
lnk_blob.x_hist_bins_count = 0;
|
|
lnk_blob.x_hist_bins = NULL;
|
|
lnk_blob.y_hist_bins_count = 0;
|
|
lnk_blob.y_hist_bins = NULL;
|
|
// These store the current average accumulation.
|
|
lnk_blob.centroid_x_acc = lnk_blob.centroid_x * lnk_blob.pixels;
|
|
lnk_blob.centroid_y_acc = lnk_blob.centroid_y * lnk_blob.pixels;
|
|
lnk_blob.rotation_acc_x = cosf(lnk_blob.rotation) * lnk_blob.pixels;
|
|
lnk_blob.rotation_acc_y = sinf(lnk_blob.rotation) * lnk_blob.pixels;
|
|
lnk_blob.roundness_acc = lnk_blob.roundness * lnk_blob.pixels;
|
|
|
|
if (x_hist_bins) {
|
|
bin_up(x_hist_bins,
|
|
ptr->w,
|
|
x_hist_bins_max,
|
|
&lnk_blob.x_hist_bins,
|
|
&lnk_blob.x_hist_bins_count);
|
|
}
|
|
|
|
if (y_hist_bins) {
|
|
bin_up(y_hist_bins,
|
|
ptr->h,
|
|
y_hist_bins_max,
|
|
&lnk_blob.y_hist_bins,
|
|
&lnk_blob.y_hist_bins_count);
|
|
}
|
|
|
|
bool add_to_list = threshold_cb_arg == NULL;
|
|
if (!add_to_list) {
|
|
// Protect ourselves from caught exceptions in the callback
|
|
// code from freeing our fb_alloc() stack.
|
|
fb_alloc_mark();
|
|
fb_alloc_mark_permanent();
|
|
add_to_list = threshold_cb(threshold_cb_arg, &lnk_blob);
|
|
fb_alloc_free_till_mark_past_mark_permanent();
|
|
}
|
|
|
|
if (add_to_list) {
|
|
list_push_back(out, &lnk_blob);
|
|
} else {
|
|
if (lnk_blob.x_hist_bins) {
|
|
m_free(lnk_blob.x_hist_bins);
|
|
}
|
|
if (lnk_blob.y_hist_bins) {
|
|
m_free(lnk_blob.y_hist_bins);
|
|
}
|
|
}
|
|
}
|
|
|
|
x = old_x;
|
|
y = old_y;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
default: {
|
|
break;
|
|
}
|
|
}
|
|
|
|
code += 1;
|
|
}
|
|
|
|
lifo_free(&lifo);
|
|
if (y_hist_bins) {
|
|
fb_free();
|
|
}
|
|
if (x_hist_bins) {
|
|
fb_free();
|
|
}
|
|
fb_free(); // bitmap
|
|
|
|
if (merge) {
|
|
for (;;) {
|
|
bool merge_occured = false;
|
|
|
|
list_t out_temp;
|
|
list_init(&out_temp, sizeof(find_blobs_list_lnk_data_t));
|
|
|
|
while (list_size(out)) {
|
|
find_blobs_list_lnk_data_t lnk_blob;
|
|
list_pop_front(out, &lnk_blob);
|
|
|
|
for (size_t k = 0, l = list_size(out); k < l; k++) {
|
|
find_blobs_list_lnk_data_t tmp_blob;
|
|
list_pop_front(out, &tmp_blob);
|
|
|
|
rectangle_t temp;
|
|
temp.x = __SSAT(tmp_blob.rect.x - margin, 16);
|
|
temp.y = __SSAT(tmp_blob.rect.y - margin, 16);
|
|
temp.w = __USAT(tmp_blob.rect.w + (margin * 2), 15);
|
|
temp.h = __USAT(tmp_blob.rect.h + (margin * 2), 15);
|
|
|
|
if (rectangle_overlap(&(lnk_blob.rect), &temp)
|
|
&& ((merge_cb_arg == NULL) || merge_cb(merge_cb_arg, &lnk_blob, &tmp_blob))) {
|
|
// Have to merge these first before merging rects.
|
|
if (x_hist_bins_max) {
|
|
merge_bins(lnk_blob.rect.x,
|
|
lnk_blob.rect.x + lnk_blob.rect.w - 1,
|
|
&lnk_blob.x_hist_bins,
|
|
&lnk_blob.x_hist_bins_count,
|
|
tmp_blob.rect.x,
|
|
tmp_blob.rect.x + tmp_blob.rect.w - 1,
|
|
&tmp_blob.x_hist_bins,
|
|
&tmp_blob.x_hist_bins_count,
|
|
x_hist_bins_max);
|
|
}
|
|
if (y_hist_bins_max) {
|
|
merge_bins(lnk_blob.rect.y,
|
|
lnk_blob.rect.y + lnk_blob.rect.h - 1,
|
|
&lnk_blob.y_hist_bins,
|
|
&lnk_blob.y_hist_bins_count,
|
|
tmp_blob.rect.y,
|
|
tmp_blob.rect.y + tmp_blob.rect.h - 1,
|
|
&tmp_blob.y_hist_bins,
|
|
&tmp_blob.y_hist_bins_count,
|
|
y_hist_bins_max);
|
|
}
|
|
// Merge corners...
|
|
for (int i = 0; i < FIND_BLOBS_CORNERS_RESOLUTION; i++) {
|
|
float z_dst = (lnk_blob.corners[i].x * cos_table[FIND_BLOBS_ANGLE_RESOLUTION * i]) +
|
|
(lnk_blob.corners[i].y * sin_table[FIND_BLOBS_ANGLE_RESOLUTION * i]);
|
|
float z_src = (tmp_blob.corners[i].x * cos_table[FIND_BLOBS_ANGLE_RESOLUTION * i]) +
|
|
(tmp_blob.corners[i].y * cos_table[FIND_BLOBS_ANGLE_RESOLUTION * i]);
|
|
if (z_src < z_dst) {
|
|
lnk_blob.corners[i].x = tmp_blob.corners[i].x;
|
|
lnk_blob.corners[i].y = tmp_blob.corners[i].y;
|
|
}
|
|
}
|
|
// Merge rects...
|
|
rectangle_united(&(lnk_blob.rect), &(tmp_blob.rect));
|
|
// Merge counters...
|
|
lnk_blob.pixels += tmp_blob.pixels; // won't overflow
|
|
lnk_blob.perimeter += tmp_blob.perimeter; // won't overflow
|
|
lnk_blob.code |= tmp_blob.code; // won't overflow
|
|
lnk_blob.count += tmp_blob.count; // won't overflow
|
|
// Merge accumulators...
|
|
lnk_blob.centroid_x_acc += tmp_blob.centroid_x_acc;
|
|
lnk_blob.centroid_y_acc += tmp_blob.centroid_y_acc;
|
|
lnk_blob.rotation_acc_x += tmp_blob.rotation_acc_x;
|
|
lnk_blob.rotation_acc_y += tmp_blob.rotation_acc_y;
|
|
lnk_blob.roundness_acc += tmp_blob.roundness_acc;
|
|
// Compute current values...
|
|
lnk_blob.centroid_x = lnk_blob.centroid_x_acc / lnk_blob.pixels;
|
|
lnk_blob.centroid_y = lnk_blob.centroid_y_acc / lnk_blob.pixels;
|
|
lnk_blob.rotation = fast_atan2f(lnk_blob.rotation_acc_y / lnk_blob.pixels,
|
|
lnk_blob.rotation_acc_x / lnk_blob.pixels);
|
|
lnk_blob.roundness = lnk_blob.roundness_acc / lnk_blob.pixels;
|
|
merge_occured = true;
|
|
} else {
|
|
list_push_back(out, &tmp_blob);
|
|
}
|
|
}
|
|
|
|
list_push_back(&out_temp, &lnk_blob);
|
|
}
|
|
|
|
list_copy(out, &out_temp);
|
|
|
|
if (!merge_occured) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void imlib_flood_fill_int(image_t *out, image_t *img, int x, int y,
|
|
int seed_threshold, int floating_threshold,
|
|
flood_fill_call_back_t cb, void *data) {
|
|
lifo_t lifo;
|
|
size_t lifo_len;
|
|
lifo_alloc_all(&lifo, &lifo_len, sizeof(xylr_t));
|
|
|
|
switch (img->pixfmt) {
|
|
case PIXFORMAT_BINARY: {
|
|
for (int seed_pixel = IMAGE_GET_BINARY_PIXEL(img, x, y);;) {
|
|
int left = x, right = x;
|
|
uint32_t *row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, y);
|
|
uint32_t *out_row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(out, y);
|
|
|
|
while ((left > 0)
|
|
&& (!IMAGE_GET_BINARY_PIXEL_FAST(out_row, left - 1))
|
|
&& COLOR_BOUND_BINARY(IMAGE_GET_BINARY_PIXEL_FAST(row, left - 1), seed_pixel, seed_threshold)
|
|
&& COLOR_BOUND_BINARY(IMAGE_GET_BINARY_PIXEL_FAST(row, left - 1),
|
|
IMAGE_GET_BINARY_PIXEL_FAST(row, left), floating_threshold)) {
|
|
left--;
|
|
}
|
|
|
|
while ((right < (img->w - 1))
|
|
&& (!IMAGE_GET_BINARY_PIXEL_FAST(out_row, right + 1))
|
|
&& COLOR_BOUND_BINARY(IMAGE_GET_BINARY_PIXEL_FAST(row, right + 1), seed_pixel, seed_threshold)
|
|
&& COLOR_BOUND_BINARY(IMAGE_GET_BINARY_PIXEL_FAST(row, right + 1),
|
|
IMAGE_GET_BINARY_PIXEL_FAST(row, right), floating_threshold)) {
|
|
right++;
|
|
}
|
|
|
|
for (int i = left; i <= right; i++) {
|
|
IMAGE_SET_BINARY_PIXEL_FAST(out_row, i);
|
|
}
|
|
|
|
int top_left = left;
|
|
int bot_left = left;
|
|
bool break_out = false;
|
|
for (;;) {
|
|
if (lifo_size(&lifo) < lifo_len) {
|
|
uint32_t *old_row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, y);
|
|
|
|
if (y > 0) {
|
|
row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, y - 1);
|
|
out_row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(out, y - 1);
|
|
|
|
bool recurse = false;
|
|
for (int i = top_left; i <= right; i++) {
|
|
if ((!IMAGE_GET_BINARY_PIXEL_FAST(out_row, i))
|
|
&& COLOR_BOUND_BINARY(IMAGE_GET_BINARY_PIXEL_FAST(row, i), seed_pixel, seed_threshold)
|
|
&& COLOR_BOUND_BINARY(IMAGE_GET_BINARY_PIXEL_FAST(row, i),
|
|
IMAGE_GET_BINARY_PIXEL_FAST(old_row, i), floating_threshold)) {
|
|
xylr_t context;
|
|
context.x = x;
|
|
context.y = y;
|
|
context.l = left;
|
|
context.r = right;
|
|
context.t_l = i + 1; // Don't test the same pixel again...
|
|
context.b_l = bot_left;
|
|
lifo_enqueue(&lifo, &context);
|
|
x = i;
|
|
y = y - 1;
|
|
recurse = true;
|
|
break;
|
|
}
|
|
}
|
|
if (recurse) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (y < (img->h - 1)) {
|
|
row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, y + 1);
|
|
out_row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(out, y + 1);
|
|
|
|
bool recurse = false;
|
|
for (int i = bot_left; i <= right; i++) {
|
|
if ((!IMAGE_GET_BINARY_PIXEL_FAST(out_row, i))
|
|
&& COLOR_BOUND_BINARY(IMAGE_GET_BINARY_PIXEL_FAST(row, i), seed_pixel, seed_threshold)
|
|
&& COLOR_BOUND_BINARY(IMAGE_GET_BINARY_PIXEL_FAST(row, i),
|
|
IMAGE_GET_BINARY_PIXEL_FAST(old_row, i), floating_threshold)) {
|
|
xylr_t context;
|
|
context.x = x;
|
|
context.y = y;
|
|
context.l = left;
|
|
context.r = right;
|
|
context.t_l = top_left;
|
|
context.b_l = i + 1; // Don't test the same pixel again...
|
|
lifo_enqueue(&lifo, &context);
|
|
x = i;
|
|
y = y + 1;
|
|
recurse = true;
|
|
break;
|
|
}
|
|
}
|
|
if (recurse) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (cb) {
|
|
cb(img, y, left, right, data);
|
|
}
|
|
|
|
if (!lifo_size(&lifo)) {
|
|
break_out = true;
|
|
break;
|
|
}
|
|
|
|
xylr_t context;
|
|
lifo_dequeue(&lifo, &context);
|
|
x = context.x;
|
|
y = context.y;
|
|
left = context.l;
|
|
right = context.r;
|
|
top_left = context.t_l;
|
|
bot_left = context.b_l;
|
|
}
|
|
|
|
if (break_out) {
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case PIXFORMAT_GRAYSCALE: {
|
|
for (int seed_pixel = IMAGE_GET_GRAYSCALE_PIXEL(img, x, y);;) {
|
|
int left = x, right = x;
|
|
uint8_t *row = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, y);
|
|
uint32_t *out_row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(out, y);
|
|
|
|
while ((left > 0)
|
|
&& (!IMAGE_GET_BINARY_PIXEL_FAST(out_row, left - 1))
|
|
&& COLOR_BOUND_GRAYSCALE(IMAGE_GET_GRAYSCALE_PIXEL_FAST(row, left - 1), seed_pixel, seed_threshold)
|
|
&& COLOR_BOUND_GRAYSCALE(IMAGE_GET_GRAYSCALE_PIXEL_FAST(row, left - 1),
|
|
IMAGE_GET_GRAYSCALE_PIXEL_FAST(row, left), floating_threshold)) {
|
|
left--;
|
|
}
|
|
|
|
while ((right < (img->w - 1))
|
|
&& (!IMAGE_GET_BINARY_PIXEL_FAST(out_row, right + 1))
|
|
&& COLOR_BOUND_GRAYSCALE(IMAGE_GET_GRAYSCALE_PIXEL_FAST(row, right + 1), seed_pixel, seed_threshold)
|
|
&& COLOR_BOUND_GRAYSCALE(IMAGE_GET_GRAYSCALE_PIXEL_FAST(row, right + 1),
|
|
IMAGE_GET_GRAYSCALE_PIXEL_FAST(row, right), floating_threshold)) {
|
|
right++;
|
|
}
|
|
|
|
for (int i = left; i <= right; i++) {
|
|
IMAGE_SET_BINARY_PIXEL_FAST(out_row, i);
|
|
}
|
|
|
|
int top_left = left;
|
|
int bot_left = left;
|
|
bool break_out = false;
|
|
for (;;) {
|
|
if (lifo_size(&lifo) < lifo_len) {
|
|
uint8_t *old_row = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, y);
|
|
|
|
if (y > 0) {
|
|
row = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, y - 1);
|
|
out_row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(out, y - 1);
|
|
|
|
bool recurse = false;
|
|
for (int i = top_left; i <= right; i++) {
|
|
if ((!IMAGE_GET_BINARY_PIXEL_FAST(out_row, i))
|
|
&& COLOR_BOUND_GRAYSCALE(IMAGE_GET_GRAYSCALE_PIXEL_FAST(row, i), seed_pixel, seed_threshold)
|
|
&& COLOR_BOUND_GRAYSCALE(IMAGE_GET_GRAYSCALE_PIXEL_FAST(row, i),
|
|
IMAGE_GET_GRAYSCALE_PIXEL_FAST(old_row, i), floating_threshold)) {
|
|
xylr_t context;
|
|
context.x = x;
|
|
context.y = y;
|
|
context.l = left;
|
|
context.r = right;
|
|
context.t_l = i + 1; // Don't test the same pixel again...
|
|
context.b_l = bot_left;
|
|
lifo_enqueue(&lifo, &context);
|
|
x = i;
|
|
y = y - 1;
|
|
recurse = true;
|
|
break;
|
|
}
|
|
}
|
|
if (recurse) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (y < (img->h - 1)) {
|
|
row = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, y + 1);
|
|
out_row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(out, y + 1);
|
|
|
|
bool recurse = false;
|
|
for (int i = bot_left; i <= right; i++) {
|
|
if ((!IMAGE_GET_BINARY_PIXEL_FAST(out_row, i))
|
|
&& COLOR_BOUND_GRAYSCALE(IMAGE_GET_GRAYSCALE_PIXEL_FAST(row, i), seed_pixel, seed_threshold)
|
|
&& COLOR_BOUND_GRAYSCALE(IMAGE_GET_GRAYSCALE_PIXEL_FAST(row, i),
|
|
IMAGE_GET_GRAYSCALE_PIXEL_FAST(old_row, i), floating_threshold)) {
|
|
xylr_t context;
|
|
context.x = x;
|
|
context.y = y;
|
|
context.l = left;
|
|
context.r = right;
|
|
context.t_l = top_left;
|
|
context.b_l = i + 1; // Don't test the same pixel again...
|
|
lifo_enqueue(&lifo, &context);
|
|
x = i;
|
|
y = y + 1;
|
|
recurse = true;
|
|
break;
|
|
}
|
|
}
|
|
if (recurse) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (cb) {
|
|
cb(img, y, left, right, data);
|
|
}
|
|
|
|
if (!lifo_size(&lifo)) {
|
|
break_out = true;
|
|
break;
|
|
}
|
|
|
|
xylr_t context;
|
|
lifo_dequeue(&lifo, &context);
|
|
x = context.x;
|
|
y = context.y;
|
|
left = context.l;
|
|
right = context.r;
|
|
top_left = context.t_l;
|
|
bot_left = context.b_l;
|
|
}
|
|
|
|
if (break_out) {
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case PIXFORMAT_RGB565: {
|
|
for (int seed_pixel = IMAGE_GET_RGB565_PIXEL(img, x, y);;) {
|
|
int left = x, right = x;
|
|
uint16_t *row = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, y);
|
|
uint32_t *out_row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(out, y);
|
|
|
|
while ((left > 0)
|
|
&& (!IMAGE_GET_BINARY_PIXEL_FAST(out_row, left - 1))
|
|
&& COLOR_BOUND_RGB565(IMAGE_GET_RGB565_PIXEL_FAST(row, left - 1), seed_pixel, seed_threshold)
|
|
&& COLOR_BOUND_RGB565(IMAGE_GET_RGB565_PIXEL_FAST(row, left - 1),
|
|
IMAGE_GET_RGB565_PIXEL_FAST(row, left), floating_threshold)) {
|
|
left--;
|
|
}
|
|
|
|
while ((right < (img->w - 1))
|
|
&& (!IMAGE_GET_BINARY_PIXEL_FAST(out_row, right + 1))
|
|
&& COLOR_BOUND_RGB565(IMAGE_GET_RGB565_PIXEL_FAST(row, right + 1), seed_pixel, seed_threshold)
|
|
&& COLOR_BOUND_RGB565(IMAGE_GET_RGB565_PIXEL_FAST(row, right + 1),
|
|
IMAGE_GET_RGB565_PIXEL_FAST(row, right), floating_threshold)) {
|
|
right++;
|
|
}
|
|
|
|
for (int i = left; i <= right; i++) {
|
|
IMAGE_SET_BINARY_PIXEL_FAST(out_row, i);
|
|
}
|
|
|
|
int top_left = left;
|
|
int bot_left = left;
|
|
bool break_out = false;
|
|
for (;;) {
|
|
if (lifo_size(&lifo) < lifo_len) {
|
|
uint16_t *old_row = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, y);
|
|
|
|
if (y > 0) {
|
|
row = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, y - 1);
|
|
out_row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(out, y - 1);
|
|
|
|
bool recurse = false;
|
|
for (int i = top_left; i <= right; i++) {
|
|
if ((!IMAGE_GET_BINARY_PIXEL_FAST(out_row, i))
|
|
&& COLOR_BOUND_RGB565(IMAGE_GET_RGB565_PIXEL_FAST(row, i), seed_pixel, seed_threshold)
|
|
&& COLOR_BOUND_RGB565(IMAGE_GET_RGB565_PIXEL_FAST(row, i),
|
|
IMAGE_GET_RGB565_PIXEL_FAST(old_row, i), floating_threshold)) {
|
|
xylr_t context;
|
|
context.x = x;
|
|
context.y = y;
|
|
context.l = left;
|
|
context.r = right;
|
|
context.t_l = i + 1; // Don't test the same pixel again...
|
|
context.b_l = bot_left;
|
|
lifo_enqueue(&lifo, &context);
|
|
x = i;
|
|
y = y - 1;
|
|
recurse = true;
|
|
break;
|
|
}
|
|
}
|
|
if (recurse) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (y < (img->h - 1)) {
|
|
row = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, y + 1);
|
|
out_row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(out, y + 1);
|
|
|
|
bool recurse = false;
|
|
for (int i = bot_left; i <= right; i++) {
|
|
if ((!IMAGE_GET_BINARY_PIXEL_FAST(out_row, i))
|
|
&& COLOR_BOUND_RGB565(IMAGE_GET_RGB565_PIXEL_FAST(row, i), seed_pixel, seed_threshold)
|
|
&& COLOR_BOUND_RGB565(IMAGE_GET_RGB565_PIXEL_FAST(row, i),
|
|
IMAGE_GET_RGB565_PIXEL_FAST(old_row, i), floating_threshold)) {
|
|
xylr_t context;
|
|
context.x = x;
|
|
context.y = y;
|
|
context.l = left;
|
|
context.r = right;
|
|
context.t_l = top_left;
|
|
context.b_l = i + 1; // Don't test the same pixel again...
|
|
lifo_enqueue(&lifo, &context);
|
|
x = i;
|
|
y = y + 1;
|
|
recurse = true;
|
|
break;
|
|
}
|
|
}
|
|
if (recurse) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (cb) {
|
|
cb(img, y, left, right, data);
|
|
}
|
|
|
|
if (!lifo_size(&lifo)) {
|
|
break_out = true;
|
|
break;
|
|
}
|
|
|
|
xylr_t context;
|
|
lifo_dequeue(&lifo, &context);
|
|
x = context.x;
|
|
y = context.y;
|
|
left = context.l;
|
|
right = context.r;
|
|
top_left = context.t_l;
|
|
bot_left = context.b_l;
|
|
}
|
|
|
|
if (break_out) {
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
default: {
|
|
break;
|
|
}
|
|
}
|
|
|
|
lifo_free(&lifo);
|
|
}
|