Skip to content
Merged
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
21 changes: 0 additions & 21 deletions data/darktableconfig.xml.in
Original file line number Diff line number Diff line change
Expand Up @@ -3586,27 +3586,6 @@
<shortdescription>AI mask binarization threshold</shortdescription>
<longdescription>sigmoid threshold for converting AI mask to binary. higher values (0.6-0.7) tighten boundaries, lower values include more area.</longdescription>
</dtconfig>
<dtconfig>
<name>plugins/darkroom/masks/object/morph_radius</name>
<type min="0" max="5">int</type>
<default>3</default>
<shortdescription>AI mask morphological cleanup radius</shortdescription>
<longdescription>radius of structuring element for morphological open+close cleanup. removes small protrusions and fills small holes in the mask. 0 = disabled, 1-3 = typical values.</longdescription>
</dtconfig>
<dtconfig>
<name>plugins/darkroom/masks/object/guided_radius</name>
<type min="0" max="20">int</type>
<default>5</default>
<shortdescription>AI mask guided filter radius</shortdescription>
<longdescription>radius of the guided filter used to snap the mask boundary to image edges. larger values produce smoother boundaries. 0 = disabled.</longdescription>
</dtconfig>
<dtconfig>
<name>plugins/darkroom/masks/object/guided_eps</name>
<type min="0.001" max="1.0">float</type>
<default>0.01</default>
<shortdescription>AI mask guided filter edge sensitivity</shortdescription>
<longdescription>edge sensitivity for the guided filter. smaller values preserve finer edges. 0.001 = very sharp, 0.1 = soft, 1.0 = nearly no edge preservation.</longdescription>
</dtconfig>
<dtconfig>
<name>plugins/darkroom/masks/object/render_size</name>
<type min="1024">int</type>
Expand Down
161 changes: 0 additions & 161 deletions src/develop/masks/object.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
#include "common/ai_models.h"
#include "common/colorspaces.h"
#include "common/debug.h"
#include "common/guided_filter.h"
#include "common/mipmap_cache.h"
#include "common/ras2vect.h"
#include "control/conf.h"
Expand All @@ -40,9 +39,6 @@
#define CONF_OBJECT_MODEL_KEY "plugins/darkroom/masks/object/model"
#define CONF_OBJECT_THRESHOLD_KEY "plugins/darkroom/masks/object/threshold"
#define CONF_OBJECT_REFINE_KEY "plugins/darkroom/masks/object/refine_passes"
#define CONF_OBJECT_MORPH_KEY "plugins/darkroom/masks/object/morph_radius"
#define CONF_OBJECT_GUIDED_RADIUS_KEY "plugins/darkroom/masks/object/guided_radius"
#define CONF_OBJECT_GUIDED_EPS_KEY "plugins/darkroom/masks/object/guided_eps"
#define CONF_OBJECT_CLEANUP_KEY "plugins/darkroom/masks/object/cleanup"
#define CONF_OBJECT_SMOOTHING_KEY "plugins/darkroom/masks/object/smoothing"
#define CONF_OBJECT_FEATHER_KEY "plugins/darkroom/masks/object/feather"
Expand Down Expand Up @@ -535,149 +531,6 @@ static void _keep_seed_component(float *mask,
g_free(labels);
}

// morphological erode: output pixel is 1 only if all pixels in the
// square structuring element of given radius are 1
static void _morph_erode(const uint8_t *src,
uint8_t *dst,
const int w,
const int h,
const int radius)
{
for(int y = 0; y < h; y++)
{
const int y0 = MAX(y - radius, 0);
const int y1 = MIN(y + radius, h - 1);
for(int x = 0; x < w; x++)
{
const int x0 = MAX(x - radius, 0);
const int x1 = MIN(x + radius, w - 1);
uint8_t val = 1;
for(int ny = y0; ny <= y1 && val; ny++)
for(int nx = x0; nx <= x1 && val; nx++)
if(!src[ny * w + nx]) val = 0;
dst[y * w + x] = val;
}
}
}

// morphological dilate: output pixel is 1 if any pixel in the
// square structuring element of given radius is 1
static void _morph_dilate(const uint8_t *src,
uint8_t *dst,
const int w,
const int h,
const int radius)
{
for(int y = 0; y < h; y++)
{
const int y0 = MAX(y - radius, 0);
const int y1 = MIN(y + radius, h - 1);
for(int x = 0; x < w; x++)
{
const int x0 = MAX(x - radius, 0);
const int x1 = MIN(x + radius, w - 1);
uint8_t val = 0;
for(int ny = y0; ny <= y1 && !val; ny++)
for(int nx = x0; nx <= x1 && !val; nx++)
if(src[ny * w + nx]) val = 1;
dst[y * w + x] = val;
}
}
}

// morphological open+close on a float mask,
// open (erode->dilate) removes small protrusions/bridges,
// close (dilate->erode) fills small holes/gaps
static void _morph_open_close(float *mask,
const int w,
const int h,
const float threshold,
const int radius)
{
if(radius <= 0) return;

const size_t n = (size_t)w * h;
uint8_t *bin = g_try_malloc(n);
uint8_t *tmp = g_try_malloc(n);
if(!bin || !tmp)
{
g_free(bin);
g_free(tmp);
return;
}

// binarize
for(size_t i = 0; i < n; i++)
bin[i] = mask[i] > threshold ? 1 : 0;

// open: erode into tmp, then dilate back into bin
_morph_erode(bin, tmp, w, h, radius);
_morph_dilate(tmp, bin, w, h, radius);

// close: dilate into tmp, then erode back into bin
_morph_dilate(bin, tmp, w, h, radius);
_morph_erode(tmp, bin, w, h, radius);

// apply result back to float mask
for(size_t i = 0; i < n; i++)
{
if(bin[i] && mask[i] <= threshold)
mask[i] = 1.0f; // filled by close
else if(!bin[i] && mask[i] > threshold)
mask[i] = 0.0f; // removed by open
}

g_free(bin);
g_free(tmp);
}

// edge-aware mask refinement using guided filter: smooths the mask in
// flat regions while preserving sharp transitions at image edges.
// the stored RGB image is used as the guide
static void _guided_filter_refine(float *mask,
const int mw,
const int mh,
const uint8_t *rgb,
const int rgb_w,
const int rgb_h,
const int radius,
const float sqrt_eps)
{
if(!rgb || rgb_w < 3 || rgb_h < 3)
return;
if(mw != rgb_w || mh != rgb_h)
return;

const size_t npix = (size_t)mw * mh;

// convert uint8 RGB to float RGBA guide (guided_filter expects 4ch)
float *guide = dt_alloc_align_float(npix * 4);
if(!guide) return;

for(size_t i = 0; i < npix; i++)
{
guide[i * 4 + 0] = (float)rgb[i * 3 + 0] / 255.0f;
guide[i * 4 + 1] = (float)rgb[i * 3 + 1] / 255.0f;
guide[i * 4 + 2] = (float)rgb[i * 3 + 2] / 255.0f;
guide[i * 4 + 3] = 0.0f;
}

// run guided filter: smooths mask but preserves edges from the guide
float *mask_bak = dt_alloc_align_float(npix);
if(!mask_bak)
{
dt_free_align(guide);
return;
}

memcpy(mask_bak, mask, npix * sizeof(float));
guided_filter(guide, mask_bak, mask, mw, mh, 4,
radius, sqrt_eps, 1.0f, 0.0f, 1.0f);

dt_free_align(mask_bak);
dt_free_align(guide);
}

// run the decoder with accumulated points and update the cached mask
static void _run_decoder(dt_masks_form_gui_t *gui)
{
Expand Down Expand Up @@ -760,22 +613,8 @@ static void _run_decoder(dt_masks_form_gui_t *gui)
const float threshold = CLAMP(dt_conf_get_float(CONF_OBJECT_THRESHOLD_KEY),
0.3f, 0.9f);

// guided filter edge refinement: snap mask boundary to image edges
const int gf_radius = CLAMP(dt_conf_get_int(CONF_OBJECT_GUIDED_RADIUS_KEY),
0, 20);
const float gf_eps = CLAMP(dt_conf_get_float(CONF_OBJECT_GUIDED_EPS_KEY),
0.001f, 1.0f);
if(gf_radius > 0 && d->encode_rgb)
_guided_filter_refine(mask, mw, mh,
d->encode_rgb, d->encode_rgb_w, d->encode_rgb_h,
gf_radius, sqrtf(gf_eps));

_keep_seed_component(mask, mw, mh, threshold, seed_x, seed_y);

// morphological open+close to remove small protrusions and fill holes
const int morph_radius = CLAMP(dt_conf_get_int(CONF_OBJECT_MORPH_KEY), 0, 5);
_morph_open_close(mask, mw, mh, threshold, morph_radius);

g_free(d->mask);
d->mask = mask;
d->mask_w = mw;
Expand Down
Loading