Commit d43c5c07 authored by Sven Neumann's avatar Sven Neumann Committed by Sven Neumann

applied patch from Gerald Friedland and Tobias Lenz. Introduces a hash

2005-09-25  Sven Neumann  <sven@gimp.org>

	* app/base/siox.[ch]: applied patch from Gerald Friedland and
	Tobias Lenz. Introduces a hash table to speed up the algorithm
	and changes the post-processing so that multiple disjunct objects
	can be selected.

	* app/tools/gimpforegroundselecttool.c: changed tooltip, the tool
	doesn't any longer extract single objects only.
parent 3d0adbf1
2005-09-25 Sven Neumann <sven@gimp.org>
* app/base/siox.[ch]: applied patch from Gerald Friedland and
Tobias Lenz. Introduces a hash table to speed up the algorithm
and changes the post-processing so that multiple disjunct objects
can be selected.
* app/tools/gimpforegroundselecttool.c: changed tooltip, the tool
doesn't any longer extract single objects only.
2005-09-25 Sven Neumann <sven@gimp.org>
* libgimpwidgets/gimpwidgetsenums.[ch]: removed GimpZoomWidgetType.
......
......@@ -14,7 +14,8 @@
* Algorithm idea by Gerald Friedland.
* This implementation is Copyright (C) 2005
* by Gerald Friedland <fland@inf.fu-berlin.de>
* and Kristian Jantz <jantz@inf.fu-berlin.de>.
* and Kristian Jantz <jantz@inf.fu-berlin.de>
* and Tobias Lenz <tlenz@inf.fu-berlin.de>.
*
* Adapted for GIMP by Sven Neumann <sven@gimp.org>
*
......@@ -62,9 +63,23 @@
#define SIOX_HIGH 254
/* FIXME: turn this into an enum */
#define SIOX_DRB_ADD 0
#define SIOX_DRB_SUBTRACT 1
/* #define SIOX_DEBUG */
/* A struct that holds the classification result */
typedef struct
{
gfloat bgdist;
gfloat fgdist;
} classresult;
/* Simulate a java.util.ArrayList */
/* Could be improved. At the moment we are wasting a node per list and
......@@ -308,7 +323,7 @@ stageone (lab *points,
/* Stage two of modified KD-Tree algorithm */
/* This is very similar to stageone... but in future there will be more
/* This is very similar to stageone... but in future there may be more
* differences => not integrated into method stageone()
*/
static void
......@@ -356,7 +371,7 @@ stagetwo (lab *points,
gfloat pivot = (min + max) / 2.0;
#ifdef SIOX_DEBUG
g_printerr ("max=%f min=%f pivot=%f\n", max, min, pivot);
g_printerr ("siox.c: max=%f min=%f pivot=%f\n", max, min, pivot);
#endif
for (i = 0; i < length; i++)
......@@ -418,8 +433,8 @@ stagetwo (lab *points,
point->b /= length;
#ifdef SIOX_DEBUG
g_printerr ("cluster=%f, %f, %f sum=%d\n",
point->l, point->a, point->b, sum);
g_printerr ("siox.c: cluster=%f, %f, %f sum=%d\n",
point->l, point->a, point->b, sum);
#endif
add_to_list (clusters, point, 1, TRUE);
......@@ -500,7 +515,7 @@ create_signature (lab *input,
}
#ifdef SIOX_DEBUG
g_printerr ("step #1 -> %d clusters\n", size);
g_printerr ("siox.c: step #1 -> %d clusters\n", size);
#endif
free_list (clusters);
......@@ -516,7 +531,7 @@ create_signature (lab *input,
free_list (clusters);
#ifdef SIOX_DEBUG
g_printerr ("step #2 -> %d clusters\n", returnlength[0]);
g_printerr ("siox.c: step #2 -> %d clusters\n", returnlength[0]);
#endif
return rval;
......@@ -565,10 +580,16 @@ dilate_mask (TileManager *mask,
}
static void
/* Do not change these defines! They contain some magic!
* Must all be non-zero and FINAL must be 0xFF!
*/
#define FIND_BLOB_SELECTED 0x1
#define FIND_BLOB_FORCEFG 0x3
#define FIND_BLOB_VISITED 0x7
#define FIND_BLOB_FINAL 0xFF
static inline void
threshold_mask (TileManager *mask,
guchar maskval,
guchar newval,
gint x,
gint y,
gint width,
......@@ -581,24 +602,37 @@ threshold_mask (TileManager *mask,
pixel_region_init (&region, mask, x, y, width, height, TRUE);
for (pr = pixel_regions_register (1, &region);
pr != NULL;
pr = pixel_regions_process (pr))
pr != NULL; pr = pixel_regions_process (pr))
{
guchar *data = region.data;
for (row = 0; row < region.h; row++)
{
guchar *d = data;
guchar *d = data;
/* everything that fits the mask is in the image*/
/* everything that fits the mask is in the image */
for (col = 0; col < region.w; col++, d++)
*d = (*d & maskval) ? newval: 0;
{
if (*d > SIOX_HIGH)
*d = FIND_BLOB_FORCEFG;
else if (*d >= 0x80)
*d = FIND_BLOB_SELECTED;
else
*d = 0;
}
data += region.rowstride;
}
}
}
}
struct blob
{
gint seedx, seedy;
gint size;
gboolean mustkeep;
};
/* This method checks out the neighbourhood of the pixel at position
* (pos_x,pos_y) in the TileManager mask, it adds the sourrounding
* pixels to the queue to allow further processing it uses maskVal to
......@@ -606,103 +640,60 @@ threshold_mask (TileManager *mask,
* are passed from above.
*/
static void
process_neighbours (GQueue *q,
gint pos_x,
gint pos_y,
TileManager *mask,
guchar maskval,
gint x,
gint y,
gint width,
gint height)
depth_first_search (TileManager *mask,
gint x,
gint y,
gint xwidth,
gint yheight,
struct blob *b,
guchar mark)
{
gint xx, yy;
guchar val;
if (pos_x + 1 < x + width)
{
read_pixel_data_1 (mask, pos_x + 1, pos_y, &val);
if (!(val & maskval))
{
g_queue_push_tail (q, GINT_TO_POINTER (pos_x + 1));
g_queue_push_tail (q, GINT_TO_POINTER (pos_y));
}
}
GSList *stack =
g_slist_prepend (g_slist_prepend (NULL, GINT_TO_POINTER (b->seedy)),
GINT_TO_POINTER (b->seedx));
if (pos_x > x)
while (stack != NULL)
{
read_pixel_data_1 (mask, pos_x - 1, pos_y, &val);
xx = GPOINTER_TO_INT (stack->data);
stack = g_slist_delete_link (stack, stack);
yy = GPOINTER_TO_INT (stack->data);
stack = g_slist_delete_link (stack, stack);
if (!(val & maskval))
read_pixel_data_1 (mask, xx, yy, &val);
if (val && (val != mark))
{
g_queue_push_tail (q, GINT_TO_POINTER (pos_x - 1));
g_queue_push_tail (q, GINT_TO_POINTER (pos_y));
}
}
if (pos_y + 1 < y + height)
{
read_pixel_data_1 (mask, pos_x, pos_y + 1, &val);
if (!(val & maskval))
{
g_queue_push_tail (q, GINT_TO_POINTER (pos_x));
g_queue_push_tail (q, GINT_TO_POINTER (pos_y + 1));
}
}
if (pos_y > y)
{
read_pixel_data_1 (mask, pos_x , pos_y - 1, &val);
if (!(val & maskval))
{
g_queue_push_tail (q, GINT_TO_POINTER (pos_x));
g_queue_push_tail (q, GINT_TO_POINTER (pos_y - 1));
}
}
}
/* This method processes every position in the queue, it finishes when
* the queeue is empty and no further pixels kann be visited.
*/
static gint
process_queue (GQueue *q,
TileManager *mask,
guchar maskval,
gint x,
gint y,
gint width,
gint height)
{
gint regioncount = 0;
while (! g_queue_is_empty (q))
{
gint pos_x = GPOINTER_TO_INT (g_queue_pop_head (q));
gint pos_y = GPOINTER_TO_INT (g_queue_pop_head (q));
guchar val;
read_pixel_data_1 (mask, pos_x, pos_y, &val);
if (val & maskval)
continue;
/* pixel is set in original selection */
if (val & 0x1)
{
val |= maskval;
write_pixel_data_1 (mask, pos_x, pos_y, &val);
regioncount++;
if (mark == FIND_BLOB_VISITED)
{
++(b->size);
if (val == FIND_BLOB_FORCEFG)
b->mustkeep = TRUE;
}
process_neighbours (q, pos_x, pos_y,
mask, maskval, x, y, width, height);
}
write_pixel_data_1 (mask, xx, yy, &mark);
if (xx > x)
stack =
g_slist_prepend (g_slist_prepend (stack, GINT_TO_POINTER (yy)),
GINT_TO_POINTER (xx - 1));
if (xx + 1 < xwidth)
stack =
g_slist_prepend (g_slist_prepend (stack, GINT_TO_POINTER (yy)),
GINT_TO_POINTER (xx + 1));
if (yy > y)
stack =
g_slist_prepend (g_slist_prepend
(stack, GINT_TO_POINTER (yy - 1)),
GINT_TO_POINTER (xx));
if (yy + 1 < yheight)
stack =
g_slist_prepend (g_slist_prepend
(stack, GINT_TO_POINTER (yy + 1)),
GINT_TO_POINTER (xx));
}
}
return regioncount;
}
/*
......@@ -719,6 +710,7 @@ process_queue (GQueue *q,
* into mask, all pixels that belong to the biggest component, are set
* to 255 any other to 0.
*/
static void
find_max_blob (TileManager *mask,
gint x,
......@@ -726,95 +718,92 @@ find_max_blob (TileManager *mask,
gint width,
gint height)
{
GQueue *q;
PixelRegion region;
gpointer pr;
gint row, col;
gint half = (width * height) / 2;
gint maxblob_x = 0;
gint maxblob_y = 0;
gint maxregion = 0;
guchar val;
/* this mask is used to flag a pixel as visited in the first pass */
const guchar visited = 0x40;
/* this mask is used to mark a pixel in the second pass */
const guchar contained = 0x80;
threshold_mask (mask, 0x80, 0x1, x, y, width, height);
GSList *list = NULL;
PixelRegion region;
gpointer pr;
gint row, col;
gint maxsize = 0;
guchar val;
q = g_queue_new ();
threshold_mask (mask, x, y, width, height);
pixel_region_init (&region, mask, x, y, width, height, TRUE);
for (pr = pixel_regions_register (1, &region);
pr != NULL && maxregion < half;
pr = pixel_regions_process (pr))
pr != NULL; pr = pixel_regions_process (pr))
{
const guchar *data = region.data;
gint pos_y = region.y;
gint pos_y = region.y;
for (row = 0; row < region.h; row++, pos_y++)
{
gint pos_x = region.x;
const guchar *d = data;
gint pos_x = region.x;
for (col = 0; col < region.w; col++, d++, pos_x++)
for (col = 0; col < region.w; col++, pos_x++)
{
gint regioncount;
read_pixel_data_1 (mask, pos_x, pos_y, &val);
read_pixel_data_1 (mask, pos_x, pos_y, &val);
if (val & visited)
continue;
/* mark current pixel as visited */
val |= visited;
write_pixel_data_1 (mask, pos_x, pos_y, &val);
if (val && (val != FIND_BLOB_VISITED))
{
struct blob *b = g_new (struct blob, 1);
/* if this pixel is not marked as selection in original image,
* skip it
*/
if (!(val & 0x1))
continue;
b->seedx = pos_x;
b->seedy = pos_y;
b->size = 0;
b->mustkeep = FALSE;
/* check out neighbourhood */
process_neighbours (q, pos_x, pos_y,
mask, visited, x, y, width, height);
depth_first_search (mask,
x, y, x + width, y + height,
b, FIND_BLOB_VISITED);
regioncount = 1 + process_queue (q, mask, visited,
x, y, width, height);
list = g_slist_prepend (list, b);
/* remember bigest regions size and coords of an element */
if (regioncount > maxregion)
{
maxregion = regioncount;
maxblob_x = pos_x;
maxblob_y = pos_y;
if (b->size > maxsize)
maxsize = b->size;
}
}
data += region.rowstride;
}
}
/* now push maxblob coords to the queue and find maxblob again,
* so that it can be set as the resulting mask
*/
/* mark startpixel as visited */
read_pixel_data_1 (mask, maxblob_x, maxblob_y, &val);
val |= contained;
write_pixel_data_1 (mask, maxblob_x, maxblob_y, &val);
while (list != NULL)
{
struct blob *b = list->data;
process_neighbours (q, maxblob_x, maxblob_y,
mask, contained, x, y, width, height);
maxregion = process_queue (q, mask, contained, x, y, width, height);
list = g_slist_delete_link (list, list);
g_queue_free (q);
depth_first_search (mask, x, y, x + width, y + height, b,
(b->mustkeep
|| (b->size * 4 > maxsize)) ? FIND_BLOB_FINAL : 0);
g_free (b);
}
}
/* set found pixel to 255 in the mask */
threshold_mask (mask, contained, 0xFF, x, y, width, height);
/* Creates a key for the hashtable from a given pixel color value */
static inline gint
create_key (const guchar *src,
gint bpp,
const guchar *colormap)
{
switch (bpp)
{
case 3: /* RGB */
case 4: /* RGBA */
return (src[RED_PIX] << 16 | src[GREEN_PIX] << 8 | src[BLUE_PIX]);
case 2:
case 1:
if (colormap) /* INDEXED(A) */
{
gint i = *src * 3;
return (colormap[i + RED_PIX] << 16 |
colormap[i + GREEN_PIX] << 8 |
colormap[i + BLUE_PIX]);
}
else /* GRAY(A) */
{
return *src;
}
default:
return 0;
}
}
static inline void
......@@ -826,6 +815,7 @@ siox_progress_update (SioxProgressFunc progress_callback,
progress_callback (progress_data, value);
}
/**
* siox_foreground_extract:
* @pixels: the tiles to extract the foreground from
......@@ -839,7 +829,7 @@ siox_progress_update (SioxProgressFunc progress_callback,
* @width: width of working area on mask
* @height: height of working area on mask
* @sensitivity: a double array with three entries specifing the accuracy,
* a good value is: { 0.66, 1.25, 2.5 }
* a good value is: { 0.64, 1.28, 2.56 }
* @smoothness: boundary smoothness (a good value is 3)
*
* Writes the resulting segmentation into @mask.
......@@ -874,6 +864,7 @@ siox_foreground_extract (TileManager *pixels,
lab *bgsig;
lab *fgsig;
gfloat limits[3];
GHashTable *pixtoclassresult;
g_return_if_fail (pixels != NULL);
g_return_if_fail (mask != NULL && tile_manager_bpp (mask) == 1);
......@@ -886,8 +877,12 @@ siox_foreground_extract (TileManager *pixels,
cpercep_init ();
pixtoclassresult = g_hash_table_new_full (g_direct_hash, g_direct_equal,
NULL, g_free);
siox_progress_update (progress_callback, progress_data, 0.0);
limits[0] = sensitivity[0];
limits[1] = sensitivity[1];
limits[2] = sensitivity[2];
......@@ -898,8 +893,7 @@ siox_foreground_extract (TileManager *pixels,
pixel_region_init (&mapPR, mask, x, y, width, height, FALSE);
for (pr = pixel_regions_register (1, &mapPR);
pr != NULL;
pr = pixel_regions_process (pr))
pr != NULL; pr = pixel_regions_process (pr))
{
const guchar *map = mapPR.data;
......@@ -935,8 +929,7 @@ siox_foreground_extract (TileManager *pixels,
pixel_region_init (&mapPR, mask, x, y, width, height, FALSE);
for (pr = pixel_regions_register (2, &srcPR, &mapPR);
pr != NULL;
pr = pixel_regions_process (pr))
pr != NULL; pr = pixel_regions_process (pr))
{
const guchar *src = srcPR.data;
const guchar *map = mapPR.data;
......@@ -985,14 +978,19 @@ siox_foreground_extract (TileManager *pixels,
siox_progress_update (progress_callback, progress_data, 0.4);
/* Classify - the slow way....Better: Tree traversation */
/* Classify - the cached way....Better: Tree traversation? */
#ifdef SIOX_DEBUG
gint hits = 0;
gint miss = 0;
#endif
pixel_region_init (&srcPR, pixels,
x - offset_x, y - offset_y, width, height, FALSE);
pixel_region_init (&mapPR, mask, x, y, width, height, TRUE);
for (pr = pixel_regions_register (2, &srcPR, &mapPR);
pr != NULL;
pr = pixel_regions_process (pr))
pr != NULL; pr = pixel_regions_process (pr))
{
const guchar *src = srcPR.data;
guchar *map = mapPR.data;
......@@ -1004,57 +1002,100 @@ siox_foreground_extract (TileManager *pixels,
for (col = 0; col < srcPR.w; col++, m++, s += bpp)
{
lab labpixel;
gboolean background;
gfloat min, d;
lab labpixel;
gfloat minbg, minfg, d;
classresult *cr;
gint key;
if (*m < SIOX_LOW || *m > SIOX_HIGH)
continue;
key = create_key (s, bpp, colormap);
/* FIXME: Do not create HashTable in here, do it globally */
cr = g_hash_table_lookup (pixtoclassresult,
GINT_TO_POINTER (key));
if (cr)
{
*m = (cr->bgdist >= cr->fgdist) ? 254 : 0;
#ifdef SIOX_DEBUG
++hits;
#endif
continue;
}
#ifdef SIOX_DEBUG
++miss;
#endif
cr = g_new0 (classresult, 1);
calc_lab (s, bpp, colormap, &labpixel);
min = euklid (&labpixel, bgsig + 0);
minbg = euklid (&labpixel, bgsig + 0);
for (i = 1; i < bgsiglen; i++)
{
d = euklid (&labpixel, bgsig + i);
if (d < min)
min = d;
if (d < minbg)
minbg = d;
}
cr->bgdist = minbg;
if (fgsiglen == 0)
{
background = (min < clustersize);
if (minbg < clustersize)
minfg = minbg + clustersize;
else
minfg = 0.00001; /* This is a guess -
now we actually require a foreground
signature, !=0 to avoid div by zero
*/
}
else
{
background = TRUE;
minfg = euklid (&labpixel, fgsig + 0);
for (i = 0; i < fgsiglen; i++)
for (i = 1; i < fgsiglen; i++)
{
d = euklid (&labpixel, fgsig + i);
if (d < min)
if (d < minfg)
{
min = d;
background = FALSE;
break;
minfg = d;
}
}
}
*m = background ? 0 : 255;
}
cr->bgdist = minbg;
cr->fgdist = minfg;