diff --git a/gst/gstvideolevels.c b/gst/gstvideolevels.c index d9513bd..8683fe1 100644 --- a/gst/gstvideolevels.c +++ b/gst/gstvideolevels.c @@ -58,14 +58,27 @@ enum PROP_LOWIN, PROP_HIGHIN, PROP_LOWOUT, - PROP_HIGHOUT - /* FILL ME */ + PROP_HIGHOUT, + PROP_AUTO, + PROP_INTERVAL/*, + PROP_NPIXSAT_LOW, + PROP_NPIXSAT_HIGH, + PROP_ROI_X, + PROP_ROI_Y, + PROP_ROI_WIDTH, + PROP_ROI_HEIGHT */ }; #define DEFAULT_PROP_LOWIN 0.0 #define DEFAULT_PROP_HIGHIN 1.0 #define DEFAULT_PROP_LOWOUT 0.0 #define DEFAULT_PROP_HIGHOUT 1.0 +#define DEFAULT_PROP_AUTO 0 +#define DEFAULT_PROP_INTERVAL (GST_SECOND / 2) +#define DEFAULT_PROP_ROW_X 0 +#define DEFAULT_PROP_ROW_Y 0 +#define DEFAULT_PROP_ROW_WIDTH 0 +#define DEFAULT_PROP_ROW_HEIGHT 0 static const GstElementDetails videolevels_details = GST_ELEMENT_DETAILS ("Video videolevels adjustment", @@ -136,6 +149,10 @@ static void gst_videolevels_reset(GstVideoLevels* filter); static void gst_videolevels_calculate_tables (GstVideoLevels * videolevels); static gboolean gst_videolevels_do_levels (GstVideoLevels * videolevels, gpointer indata, gpointer outdata); +static gboolean gst_videolevels_calculate_histogram (GstVideoLevels * videolevels, + guint16 * data); +static gboolean gst_videolevels_auto_adjust (GstVideoLevels * videolevels, + guint16 * data); /* setup debug */ GST_DEBUG_CATEGORY_STATIC (videolevels_debug); @@ -211,16 +228,22 @@ gst_videolevels_class_init (GstVideoLevelsClass * object) /* Install GObject properties */ g_object_class_install_property (obj_class, PROP_LOWIN, g_param_spec_double ("low_in", "Lower Input Level", "Lower Input Level", - 0.0, 1.0, DEFAULT_PROP_LOWIN, G_PARAM_READWRITE)); + 0.0, G_MAXUINT16, DEFAULT_PROP_LOWIN, G_PARAM_READWRITE)); g_object_class_install_property (obj_class, PROP_HIGHIN, g_param_spec_double ("upper_in", "Upper Input Level", "Upper Input Level", - 0.0, 1.0, DEFAULT_PROP_HIGHIN, G_PARAM_READWRITE)); + 0.0, G_MAXUINT16, DEFAULT_PROP_HIGHIN, G_PARAM_READWRITE)); g_object_class_install_property (obj_class, PROP_LOWOUT, g_param_spec_double ("low_out", "Lower Output Level", "Lower Output Level", - 0.0, 1.0, DEFAULT_PROP_LOWOUT, G_PARAM_READWRITE)); + 0.0, G_MAXUINT16, DEFAULT_PROP_LOWOUT, G_PARAM_READWRITE)); g_object_class_install_property (obj_class, PROP_HIGHOUT, g_param_spec_double ("upper_out", "Upper Output Level", "Upper Output Level", - 0.0, 1.0, DEFAULT_PROP_HIGHOUT, G_PARAM_READWRITE)); + 0.0, G_MAXUINT16, DEFAULT_PROP_HIGHOUT, G_PARAM_READWRITE)); + g_object_class_install_property (obj_class, PROP_AUTO, + g_param_spec_int ("auto", "Auto Adjust", "Auto adjust contrast (0): off, (1): single-shot, (2): continuous", + 0, 2, DEFAULT_PROP_AUTO, G_PARAM_READWRITE)); + g_object_class_install_property (obj_class, PROP_INTERVAL, + g_param_spec_uint64 ("interval", "Interval", "Interval of time between adjustments (in nanoseconds)", + 1, G_MAXUINT64, DEFAULT_PROP_INTERVAL, G_PARAM_READWRITE)); /* Register GstBaseTransform vmethods */ trans_class->transform_caps = GST_DEBUG_FUNCPTR (gst_videolevels_transform_caps); @@ -269,19 +292,25 @@ gst_videolevels_set_property (GObject * object, guint prop_id, switch (prop_id) { case PROP_LOWIN: videolevels->lower_input = g_value_get_double (value); - gst_videolevels_calculate_tables (videolevels); + //gst_videolevels_calculate_tables (videolevels); break; case PROP_HIGHIN: videolevels->upper_input = g_value_get_double (value); - gst_videolevels_calculate_tables (videolevels); + //gst_videolevels_calculate_tables (videolevels); break; case PROP_LOWOUT: videolevels->lower_output = g_value_get_double (value); - gst_videolevels_calculate_tables (videolevels); + //gst_videolevels_calculate_tables (videolevels); break; case PROP_HIGHOUT: videolevels->upper_output = g_value_get_double (value); - gst_videolevels_calculate_tables (videolevels); + //gst_videolevels_calculate_tables (videolevels); + break; + case PROP_AUTO: + videolevels->auto_adjust = g_value_get_int (value); + break; + case PROP_INTERVAL: + videolevels->interval = g_value_get_uint64 (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); @@ -318,6 +347,12 @@ gst_videolevels_get_property (GObject * object, guint prop_id, GValue * value, case PROP_HIGHOUT: g_value_set_double (value, videolevels->upper_output); break; + case PROP_AUTO: + g_value_set_int (value, videolevels->auto_adjust); + break; + case PROP_INTERVAL: + g_value_set_uint64 (value, videolevels->interval); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -343,11 +378,8 @@ gst_videolevels_transform_caps (GstBaseTransform * base, GstPadDirection direction, GstCaps * caps) { GstVideoLevels *videolevels; - GstCaps *static_caps; GstCaps *newcaps; GstStructure *structure; - const GValue * width; - const GValue * height; videolevels = GST_VIDEOLEVELS (base); @@ -456,8 +488,13 @@ gst_videolevels_set_caps (GstBaseTransform * base, GstCaps * incaps, levels->stride_in = GST_ROUND_UP_4 (levels->width * levels->depth_in/8); levels->stride_out = GST_ROUND_UP_4 (levels->width * levels->depth_out/8); + levels->lower_input = 0.0; + levels->upper_input = (1 << levels->bpp_in) - 1; + levels->lower_output = 0.0; + levels->upper_output = (1 << levels->bpp_out) - 1; - gst_videolevels_calculate_tables (levels); + + //gst_videolevels_calculate_tables (levels); return res; } @@ -514,24 +551,32 @@ static GstFlowReturn gst_videolevels_transform (GstBaseTransform * base, GstBuffer * inbuf, GstBuffer * outbuf) { - GstVideoLevels *filter = GST_VIDEOLEVELS (base); + GstVideoLevels *videolevels = GST_VIDEOLEVELS (base); gpointer input; gpointer output; gboolean ret; /* - * We need to lock our filter params to prevent changing + * We need to lock our videolevels params to prevent changing * caps in the middle of a transformation (nice way to get * segfaults) */ - GST_OBJECT_LOCK (filter); + GST_OBJECT_LOCK (videolevels); input = GST_BUFFER_DATA (inbuf); output = GST_BUFFER_DATA (outbuf); - ret = gst_videolevels_do_levels (filter, input, output); + if (videolevels->auto_adjust == 1) { + gst_videolevels_auto_adjust (videolevels, input); + videolevels->auto_adjust = 0; + } + else if (videolevels->auto_adjust == 2) { + gst_videolevels_auto_adjust (videolevels, input); + } - GST_OBJECT_UNLOCK (filter); + ret = gst_videolevels_do_levels (videolevels, input, output); + + GST_OBJECT_UNLOCK (videolevels); if (ret) return GST_FLOW_OK; @@ -578,6 +623,17 @@ static void gst_videolevels_reset(GstVideoLevels* videolevels) g_free (videolevels->lookup_table); videolevels->lookup_table = NULL; + + videolevels->auto_adjust = DEFAULT_PROP_AUTO; + videolevels->interval = DEFAULT_PROP_INTERVAL; + + videolevels->lower_pix_sat = 0.01f; + videolevels->upper_pix_sat = 0.01f; + + videolevels->nbins = 4096; + + g_free (videolevels->histogram); + videolevels->histogram = NULL; } /** @@ -590,7 +646,7 @@ static void gst_videolevels_calculate_tables (GstVideoLevels * videolevels) { gint i; - guint8 loIn, hiIn; + guint16 loIn, hiIn; guint8 loOut, hiOut; gdouble slope; guint8 * lut; @@ -598,27 +654,102 @@ gst_videolevels_calculate_tables (GstVideoLevels * videolevels) GST_DEBUG ("calculating lookup table"); if (!videolevels->lookup_table) { - videolevels->lookup_table = g_malloc (256); + videolevels->lookup_table = g_new (guint8, 65536); } lut = (guint8*) videolevels->lookup_table; - loIn = (guint8) videolevels->lower_input * 255; - hiIn = (guint8) videolevels->upper_input * 255; - loOut = (guint8) videolevels->lower_output * 255; - hiOut = (guint8) videolevels->upper_output * 255; + loIn = (guint16) (videolevels->lower_input * G_MAXUINT16); + hiIn = (guint16) (videolevels->upper_input * G_MAXUINT16); + loOut = (guint8) (videolevels->lower_output * G_MAXUINT8); + hiOut = (guint8) (videolevels->upper_output * G_MAXUINT8); + GST_DEBUG ("input levels (%.3f, %.3f), output levels (%.3f, %.3f)",videolevels->lower_input,videolevels->upper_input,videolevels->lower_output,videolevels->upper_output); + + GST_DEBUG ("input levels (%d, %d), output levels (%d, %d)",loIn,hiIn,loOut,hiOut); if (hiIn == loIn) slope = 0; else slope = (videolevels->upper_output - videolevels->lower_output) / (videolevels->upper_input - videolevels->lower_input); - for (i=0; iendianness_in == G_BYTE_ORDER) { + for (i = 0; i < loIn; i++) + lut[i] = loOut; + for (i = loIn; i < hiIn; i++) + lut[i] = loOut + (guint8) ( (i - loIn) * slope); + for (i = hiIn; i < 65536; i++) + lut[i] = hiOut; + } + else { + for (i = 0; i < loIn; i++) + lut[GUINT16_SWAP_LE_BE(i)] = loOut; + for (i = loIn; i < hiIn; i++) + lut[GUINT16_SWAP_LE_BE(i)] = loOut + (guint8) ((GUINT16_SWAP_LE_BE(i) - loIn) * slope); + for (i = hiIn; i < 65536; i++) + lut[GUINT16_SWAP_LE_BE(i)] = hiOut; + } + + for (i = 0; i < 65536; i += 128) + GST_DEBUG ("lut[%d / %d] = %d", i, GUINT16_SWAP_LE_BE(i), lut[i]); +} + +#define GINT_CLAMP(x, low, high) ((gint)(CLAMP((x),(low),(high)))) +#define GUINT8_CLAMP(x, low, high) ((guint8)(CLAMP((x),(low),(high)))) + +void +gst_videolevels_convert_uint16le_to_uint8(GstVideoLevels * videolevels, + guint16 * in, guint8 * out) +{ + gint i; + const gint size = videolevels->stride_in / 2 * videolevels->height; + gdouble m; + gdouble b; + const guint8 low = (guint8) videolevels->lower_output; + const guint8 high = (guint8) videolevels->upper_output; + + GST_DEBUG ("Applying linear mapping (%.3f, %.3f) -> (%.3f, %.3f)", + videolevels->lower_input, videolevels->upper_input, + videolevels->lower_output, videolevels->upper_output); + + if (videolevels->lower_input == videolevels->upper_input) + m = 0; + else + m = (videolevels->upper_output - videolevels->lower_output) / + (videolevels->upper_input - videolevels->lower_input); + + b = videolevels->lower_output - m * videolevels->lower_input; + + for (i = 0; i < size; i++) + out[i] = GUINT8_CLAMP (m * GUINT16_FROM_LE (in[i]) + b, low, high); +} + +void +gst_videolevels_convert_uint16be_to_uint8(GstVideoLevels * videolevels, + guint16 * in, guint8 * out) +{ + gint i; + const gint size = videolevels->stride_in / 2 * videolevels->height; + gdouble m; + gdouble b; + const guint8 low = (guint8) videolevels->lower_output; + const guint8 high = (guint8) videolevels->upper_output; + + GST_DEBUG ("Applying linear mapping (%.3f, %.3f) -> (%.3f, %.3f)", + videolevels->lower_input, videolevels->upper_input, + videolevels->lower_output, videolevels->upper_output); + + if (videolevels->lower_input == videolevels->upper_input) + m = 0; + else + m = (videolevels->upper_output - videolevels->lower_output) / + (videolevels->upper_input - videolevels->lower_input); + + b = videolevels->lower_output - m * videolevels->lower_input; + + for (i = 0; i < size; i++) + out[i] = GUINT8_CLAMP (m * GUINT16_FROM_BE (in[i]) + b, low, high); } /** @@ -636,27 +767,72 @@ gboolean gst_videolevels_do_levels (GstVideoLevels * videolevels, gpointer indata, gpointer outdata) { - guint8 * dst = outdata; - guint16 * src = indata; - gint r, c; - guint8 * lut = (guint8 *) videolevels->lookup_table; + if (videolevels->endianness_in == G_LITTLE_ENDIAN) + gst_videolevels_convert_uint16le_to_uint8 (videolevels, indata, outdata); + else + gst_videolevels_convert_uint16be_to_uint8 (videolevels, indata, outdata); - GST_DEBUG ("Converting frame using LUT"); - + return TRUE; + //guint8 * dst = outdata; + //guint16 * src = indata; + //gint r = 0, c = 0; + //guint8 * lut = (guint8 *) videolevels->lookup_table; + + //GST_DEBUG ("Converting frame using LUT"); + // + //for (r = 0; r < videolevels->height; r++) { + // for (c = 0; c < videolevels->width; c++) { + // dst[c+r*videolevels->stride_out] = + // lut[src[c+r*videolevels->stride_in/2]]; + // } + //} + + return TRUE; +} + +/** +* gst_videolevels_calculate_histogram +* @videolevels: #GstVideoLevels +* @data: input frame data +* +* Calculate histogram of input frame +* +* Returns: TRUE on success +*/ +gboolean +gst_videolevels_calculate_histogram (GstVideoLevels * videolevels, guint16 * data) +{ + gint * hist; + gint nbins = videolevels->nbins; + gint r; + gint c; + gfloat factor = nbins/65536.0f; + + if (videolevels->histogram == NULL) { + GST_DEBUG ("First call, allocate memory for histogram (%d bins)", nbins); + videolevels->histogram = g_new (gint, nbins); + } + + hist = videolevels->histogram; + + /* reset histogram */ + memset (hist, 0, sizeof(gint)*nbins); + + GST_DEBUG ("Calculating histogram"); if (!videolevels->is_signed_in) { if (videolevels->endianness_in == G_BYTE_ORDER) { for (r = 0; r < videolevels->height; r++) { for (c = 0; c < videolevels->width; c++) { - dst[c+r*videolevels->stride_out] = - lut[src[c+r*videolevels->stride_in/2] >> 8]; + /* GST_DEBUG ("(%d, %d) = %d, hist[%d] = %d", r, c, data [c + r * videolevels->stride_in / 2], GINT_CLAMP (data [c + r * videolevels->stride_in / 2] * factor, 0, nbins - 1), + hist [GINT_CLAMP (data [c + r * videolevels->stride_in / 2] * factor, 0, nbins - 1)] + 1);*/ + hist [GINT_CLAMP (data [c + r * videolevels->stride_in / 2] * factor, 0, nbins - 1)]++; } } } else { for (r = 0; r < videolevels->height; r++) { for (c = 0; c < videolevels->width; c++) { - dst[c+r*videolevels->stride_out] = - lut[GUINT16_FROM_BE(src[c+r*videolevels->stride_in/2]) >> 8]; + hist [GINT_CLAMP (GUINT16_FROM_BE (data [c + r * videolevels->stride_in / 2]) * factor, 0, nbins - 1)]++; } } } @@ -665,79 +841,78 @@ gst_videolevels_do_levels (GstVideoLevels * videolevels, gpointer indata, if (videolevels->endianness_in == G_BYTE_ORDER) { for (r = 0; r < videolevels->height; r++) { for (c = 0; c < videolevels->width; c++) { - dst[c+r*videolevels->stride_out] = - lut[(src[c+r*videolevels->stride_in/2]+32767) >> 8]; + hist [GINT_CLAMP ((data [c + r * videolevels->stride_in / 2] + 32767) * factor, 0, nbins - 1)]++; } - GST_DEBUG ("Row %d", r); } } else { for (r = 0; r < videolevels->height; r++) { for (c = 0; c < videolevels->width; c++) { - dst[c+r*videolevels->stride_out] = - lut[(GUINT16_FROM_BE(src[c+r*videolevels->stride_in/2])+32767) >> 8]; + hist [GINT_CLAMP ((GUINT16_FROM_BE (data [c + r * videolevels->stride_in / 2]) + 32767) * factor, 0, nbins - 1)]++; } } } } - GST_DEBUG ("DONE converting frame using LUT"); - - return TRUE; - - //gdouble loIn = videolevels->lower_input; - //gdouble hiIn = videolevels->upper_input; - //gdouble loOut = videolevels->lower_output; - //gdouble hiOut = videolevels->upper_output; - //gdouble slope; - //gdouble yintercept; - //guint32 dst_max; - //guint32 src_max; - // - ///* y=mx+b */ - ///* check for division by zero */ - //if (hiIn == loIn) - // slope=0; - //else - // slope = (hiOut-loOut)/(hiIn-loIn); - //yintercept = loOut - slope*loIn; - - //src_max = (1 << videolevels->bpp_in) - 1; - //dst_max = (1 << videolevels->bpp_out) - 1; - - /* TODO doesn't handle byte ordering */ - //if (videolevels->depth_in == 32 && videolevels->depth_out == 32) { - // guint32 * src = indata; - // guint32 * dst = outdata; - // gint r; - // gint c; - // guint32 b = dst_max * yintercept; - // gdouble m = slope*dst_max/src_max; - - - // for (r = 0; r < videolevels->height; r++) { - // for (c = 0; c < videolevels->width; c++) { - // dst[c+r*videolevels->stride_out] = m*src[c+r*videolevels->stride_in] + b; - // } - // } + //for (r = 0; r < videolevels->nbins; r++ ) { + // GST_DEBUG ("hist[%5d] = %d",r, hist[r]); //} - //else if (videolevels->depth_in == 16 && videolevels->depth_out == 8) { - // guint16 * src = indata; - // guint8 * dst = outdata; - // gint r; - // gint c; - // guint8 b = dst_max * yintercept; - // gdouble m = slope*dst_max/src_max; - - // for (r = 0; r < videolevels->height; r++) { - // for (c = 0; c < videolevels->width; c++) { - // dst[c+r*videolevels->stride_out] = m*src[c+r*videolevels->stride_in] + b; - // } - // } - //} - //else { - // return FALSE; - //} + return TRUE; +} + +/** +* gst_videolevels_auto_adjust +* @videolevels: #GstVideoLevels +* @data: input frame data +* +* Calculate lower and upper levels based on the histogram of the frame +* +* Returns: TRUE on success +*/ +gboolean +gst_videolevels_auto_adjust (GstVideoLevels * videolevels, + guint16 * data) +{ + guint npixsat; + guint sum; + gint i; + gint size; + gdouble min = 0.0; + gdouble max = (1 << videolevels->bpp_in) - 1.0; + + gst_videolevels_calculate_histogram (videolevels, data); + + size = videolevels->width * videolevels->height; + + /* pixels to saturate on low end */ + npixsat = (guint)(videolevels->lower_pix_sat * size); + sum = 0; + for (i = 0; i < videolevels->nbins; i++) { + sum += videolevels->histogram[i]; + if (sum > npixsat) { + videolevels->lower_input = + CLAMP (i / (gdouble)videolevels->nbins * max, min, max); + break; + } + } + + /* pixels to saturate on high end */ + npixsat = (guint)(videolevels->upper_pix_sat * size); + sum = 0; + for (i = videolevels->nbins - 1; i >= 0; i--) { + sum += videolevels->histogram[i]; + if (sum > npixsat) { + videolevels->upper_input = + CLAMP ((i + 1) / (gdouble)videolevels->nbins * max, min, max); + break; + } + } + + GST_DEBUG ("Contrast stretch with npixsat=%d, (%.3f, %.3f)", npixsat, + videolevels->lower_input, videolevels->upper_input); + + //gst_videolevels_calculate_tables (videolevels); + return TRUE; } diff --git a/gst/gstvideolevels.h b/gst/gstvideolevels.h index e66bfcc..ff87968 100644 --- a/gst/gstvideolevels.h +++ b/gst/gstvideolevels.h @@ -68,6 +68,13 @@ struct _GstVideoLevels /* tables */ guint8* levels_table; + + gint auto_adjust; + guint64 interval; + gfloat lower_pix_sat; + gfloat upper_pix_sat; + gint nbins; + gint * histogram; }; struct _GstVideoLevelsClass