| /* GStreamer |
| * Copyright (C) 2011 David Schleef <ds@entropywave.com> |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Library General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public |
| * License along with this library; if not, write to the |
| * Free Software Foundation, Inc., 51 Franklin Street, Suite 500, |
| * Boston, MA 02110-1335, USA. |
| */ |
| /** |
| * SECTION:element-gstpatchdetect |
| * |
| * The patchdetect element detects color patches from a color |
| * calibration chart. Currently, the patches for the 24-square |
| * Munsell ColorChecker are hard-coded into the element. When |
| * a color chart is detected in the video stream, a message is |
| * sent to the bus containing the detected color values of each |
| * of the patches. |
| * |
| * <refsect2> |
| * <title>Example launch line</title> |
| * |[ |
| * gst-launch -v dv1394src ! dvdemux ! dvdec ! patchdetect ! xvimagesink |
| * ]| |
| * </refsect2> |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include "config.h" |
| #endif |
| |
| #include <gst/gst.h> |
| #include <gst/base/gstbasetransform.h> |
| #include <gst/video/video.h> |
| #include <math.h> |
| #include <string.h> |
| #include "gstpatchdetect.h" |
| |
| GST_DEBUG_CATEGORY_STATIC (gst_patchdetect_debug_category); |
| #define GST_CAT_DEFAULT gst_patchdetect_debug_category |
| |
| /* prototypes */ |
| |
| |
| static void gst_patchdetect_set_property (GObject * object, |
| guint property_id, const GValue * value, GParamSpec * pspec); |
| static void gst_patchdetect_get_property (GObject * object, |
| guint property_id, GValue * value, GParamSpec * pspec); |
| static void gst_patchdetect_dispose (GObject * object); |
| static void gst_patchdetect_finalize (GObject * object); |
| |
| static gboolean |
| gst_patchdetect_get_unit_size (GstBaseTransform * trans, GstCaps * caps, |
| guint * size); |
| static gboolean |
| gst_patchdetect_set_caps (GstBaseTransform * trans, GstCaps * incaps, |
| GstCaps * outcaps); |
| static gboolean gst_patchdetect_start (GstBaseTransform * trans); |
| static gboolean gst_patchdetect_stop (GstBaseTransform * trans); |
| static gboolean gst_patchdetect_event (GstBaseTransform * trans, |
| GstEvent * event); |
| static GstFlowReturn gst_patchdetect_transform_ip (GstBaseTransform * trans, |
| GstBuffer * buf); |
| static gboolean gst_patchdetect_src_event (GstBaseTransform * trans, |
| GstEvent * event); |
| |
| enum |
| { |
| PROP_0 |
| }; |
| |
| /* pad templates */ |
| |
| static GstStaticPadTemplate gst_patchdetect_sink_template = |
| GST_STATIC_PAD_TEMPLATE ("sink", |
| GST_PAD_SINK, |
| GST_PAD_ALWAYS, |
| GST_STATIC_CAPS (GST_VIDEO_CAPS_YUV ("I420")) |
| ); |
| |
| static GstStaticPadTemplate gst_patchdetect_src_template = |
| GST_STATIC_PAD_TEMPLATE ("src", |
| GST_PAD_SRC, |
| GST_PAD_ALWAYS, |
| GST_STATIC_CAPS (GST_VIDEO_CAPS_YUV ("I420")) |
| ); |
| |
| |
| /* class initialization */ |
| |
| #define DEBUG_INIT(bla) \ |
| GST_DEBUG_CATEGORY_INIT (gst_patchdetect_debug_category, "patchdetect", 0, \ |
| "debug category for patchdetect element"); |
| |
| GST_BOILERPLATE_FULL (GstPatchdetect, gst_patchdetect, GstBaseTransform, |
| GST_TYPE_BASE_TRANSFORM, DEBUG_INIT); |
| |
| static void |
| gst_patchdetect_base_init (gpointer g_class) |
| { |
| GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); |
| |
| gst_element_class_add_pad_template (element_class, |
| gst_static_pad_template_get (&gst_patchdetect_sink_template)); |
| gst_element_class_add_pad_template (element_class, |
| gst_static_pad_template_get (&gst_patchdetect_src_template)); |
| |
| gst_element_class_set_metadata (element_class, "Color Patch Detector", |
| "Video/Analysis", "Detects color patches from a color calibration chart", |
| "David Schleef <ds@entropywave.com>"); |
| } |
| |
| static void |
| gst_patchdetect_class_init (GstPatchdetectClass * klass) |
| { |
| GObjectClass *gobject_class = G_OBJECT_CLASS (klass); |
| GstBaseTransformClass *base_transform_class = |
| GST_BASE_TRANSFORM_CLASS (klass); |
| |
| gobject_class->set_property = gst_patchdetect_set_property; |
| gobject_class->get_property = gst_patchdetect_get_property; |
| gobject_class->dispose = gst_patchdetect_dispose; |
| gobject_class->finalize = gst_patchdetect_finalize; |
| base_transform_class->get_unit_size = |
| GST_DEBUG_FUNCPTR (gst_patchdetect_get_unit_size); |
| base_transform_class->set_caps = GST_DEBUG_FUNCPTR (gst_patchdetect_set_caps); |
| base_transform_class->start = GST_DEBUG_FUNCPTR (gst_patchdetect_start); |
| base_transform_class->stop = GST_DEBUG_FUNCPTR (gst_patchdetect_stop); |
| base_transform_class->event = GST_DEBUG_FUNCPTR (gst_patchdetect_event); |
| base_transform_class->transform_ip = |
| GST_DEBUG_FUNCPTR (gst_patchdetect_transform_ip); |
| base_transform_class->src_event = |
| GST_DEBUG_FUNCPTR (gst_patchdetect_src_event); |
| |
| } |
| |
| static void |
| gst_patchdetect_init (GstPatchdetect * patchdetect, |
| GstPatchdetectClass * patchdetect_class) |
| { |
| } |
| |
| void |
| gst_patchdetect_set_property (GObject * object, guint property_id, |
| const GValue * value, GParamSpec * pspec) |
| { |
| g_return_if_fail (GST_IS_PATCHDETECT (object)); |
| |
| switch (property_id) { |
| default: |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); |
| break; |
| } |
| } |
| |
| void |
| gst_patchdetect_get_property (GObject * object, guint property_id, |
| GValue * value, GParamSpec * pspec) |
| { |
| g_return_if_fail (GST_IS_PATCHDETECT (object)); |
| |
| switch (property_id) { |
| default: |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); |
| break; |
| } |
| } |
| |
| void |
| gst_patchdetect_dispose (GObject * object) |
| { |
| g_return_if_fail (GST_IS_PATCHDETECT (object)); |
| |
| /* clean up as possible. may be called multiple times */ |
| |
| G_OBJECT_CLASS (parent_class)->dispose (object); |
| } |
| |
| void |
| gst_patchdetect_finalize (GObject * object) |
| { |
| g_return_if_fail (GST_IS_PATCHDETECT (object)); |
| |
| /* clean up object here */ |
| |
| G_OBJECT_CLASS (parent_class)->finalize (object); |
| } |
| |
| |
| static gboolean |
| gst_patchdetect_get_unit_size (GstBaseTransform * trans, GstCaps * caps, |
| guint * size) |
| { |
| int width, height; |
| GstVideoFormat format; |
| gboolean ret; |
| |
| ret = gst_video_format_parse_caps (caps, &format, &width, &height); |
| *size = gst_video_format_get_size (format, width, height); |
| |
| return ret; |
| } |
| |
| static gboolean |
| gst_patchdetect_set_caps (GstBaseTransform * trans, GstCaps * incaps, |
| GstCaps * outcaps) |
| { |
| GstPatchdetect *patchdetect = GST_PATCHDETECT (trans); |
| int width, height; |
| GstVideoFormat format; |
| gboolean ret; |
| |
| ret = gst_video_format_parse_caps (incaps, &format, &width, &height); |
| if (ret) { |
| patchdetect->format = format; |
| patchdetect->width = width; |
| patchdetect->height = height; |
| } |
| |
| return ret; |
| } |
| |
| static gboolean |
| gst_patchdetect_start (GstBaseTransform * trans) |
| { |
| |
| return TRUE; |
| } |
| |
| static gboolean |
| gst_patchdetect_stop (GstBaseTransform * trans) |
| { |
| |
| return TRUE; |
| } |
| |
| static gboolean |
| gst_patchdetect_event (GstBaseTransform * trans, GstEvent * event) |
| { |
| |
| return TRUE; |
| } |
| |
| typedef struct |
| { |
| guint8 *y; |
| int ystride; |
| guint8 *u; |
| int ustride; |
| guint8 *v; |
| int vstride; |
| int width; |
| int height; |
| int t; |
| } Frame; |
| |
| typedef struct |
| { |
| int y, u, v; |
| int diff_y, diff_u, diff_v; |
| gboolean match; |
| int patch_block; |
| int color; |
| int count; |
| int sum_x; |
| int sum_y; |
| } Stats; |
| |
| typedef struct |
| { |
| int r, g, b; |
| int y, u, v; |
| } Color; |
| |
| typedef struct |
| { |
| int x, y; |
| int patch1, patch2; |
| gboolean valid; |
| } Point; |
| |
| typedef struct |
| { |
| int xmin, xmax; |
| int ymin, ymax; |
| int val; |
| int y, u, v; |
| int count; |
| int cen_x, cen_y; |
| gboolean valid; |
| } Patch; |
| |
| static Color patch_colors[24] = { |
| {115, 82, 68, 92, 119, 143}, |
| {194, 150, 130, 152, 115, 148}, |
| {98, 122, 157, 119, 146, 116}, |
| {87, 108, 67, 102, 112, 120}, |
| {133, 128, 177, 130, 149, 128}, |
| {103, 189, 170, 161, 128, 91}, |
| {214, 126, 44, 135, 83, 170}, |
| {80, 91, 166, 97, 162, 120}, |
| {193, 90, 99, 113, 122, 173}, |
| {94, 60, 108, 77, 146, 141}, |
| {157, 188, 64, 164, 77, 119}, |
| {224, 163, 46, 160, 70, 160}, |
| {56, 61, 150, 73, 168, 122}, |
| {70, 148, 73, 124, 103, 97}, |
| {175, 54, 60, 85, 118, 181}, |
| {231, 199, 31, 182, 51, 149}, |
| {187, 86, 149, 112, 146, 170}, |
| {8, 133, 161, 109, 153, 72}, |
| {243, 243, 243, 225, 128, 128}, |
| {200, 200, 200, 188, 128, 128}, |
| {160, 160, 160, 153, 128, 128}, |
| {122, 122, 122, 121, 128, 128}, |
| {85, 85, 85, 89, 128, 128}, |
| {52, 52, 52, 61, 128, 128} |
| }; |
| |
| static void |
| get_block_stats (Frame * frame, int x, int y, Stats * stats) |
| { |
| int i, j; |
| guint8 *data; |
| int max; |
| int min; |
| int sum; |
| |
| max = 0; |
| min = 255; |
| sum = 0; |
| for (j = 0; j < 8; j++) { |
| data = frame->y + frame->ystride * (j + y) + x; |
| for (i = 0; i < 8; i++) { |
| max = MAX (max, data[i]); |
| min = MIN (min, data[i]); |
| sum += data[i]; |
| } |
| } |
| stats->y = sum / 64; |
| stats->diff_y = MAX (max - stats->y, stats->y - min); |
| |
| max = 0; |
| min = 255; |
| sum = 0; |
| for (j = 0; j < 4; j++) { |
| data = frame->u + frame->ustride * (j + y / 2) + x / 2; |
| for (i = 0; i < 4; i++) { |
| max = MAX (max, data[i]); |
| min = MIN (min, data[i]); |
| sum += data[i]; |
| } |
| } |
| stats->u = sum / 16; |
| stats->diff_u = MAX (max - stats->u, stats->u - min); |
| |
| max = 0; |
| min = 255; |
| sum = 0; |
| for (j = 0; j < 4; j++) { |
| data = frame->v + frame->vstride * (j + y / 2) + x / 2; |
| for (i = 0; i < 4; i++) { |
| max = MAX (max, data[i]); |
| min = MIN (min, data[i]); |
| sum += data[i]; |
| } |
| } |
| stats->v = sum / 16; |
| stats->diff_v = MAX (max - stats->v, stats->v - min); |
| |
| stats->patch_block = -1; |
| stats->match = FALSE; |
| #define MATCH 15 |
| if (stats->diff_y < MATCH && stats->diff_u < MATCH && stats->diff_v < MATCH) { |
| stats->match = TRUE; |
| } |
| } |
| |
| static void |
| paint_block (Frame * frame, int x, int y, int value) |
| { |
| int i, j; |
| guint8 *data; |
| |
| for (j = 0; j < 8; j++) { |
| data = frame->y + frame->ystride * (j + y) + x; |
| for (i = 0; i < 8; i++) { |
| data[i] = value; |
| } |
| } |
| |
| for (j = 0; j < 4; j++) { |
| data = frame->u + frame->ustride * (j + y / 2) + x / 2; |
| for (i = 0; i < 4; i++) { |
| data[i] = 128; |
| } |
| } |
| |
| for (j = 0; j < 4; j++) { |
| data = frame->v + frame->vstride * (j + y / 2) + x / 2; |
| for (i = 0; i < 4; i++) { |
| data[i] = 128; |
| } |
| } |
| } |
| |
| static gboolean |
| patch_check (Frame * frame, guint8 * patchpix, int x, int y, int w, int h) |
| { |
| int i, j; |
| |
| for (j = y; j < y + h; j++) { |
| for (i = x; i < x + w; i++) { |
| if (patchpix[j * frame->width + i] != 0) |
| return FALSE; |
| } |
| } |
| |
| return TRUE; |
| } |
| |
| static void |
| patch_start (Frame * frame, guint8 * patchpix, Patch * patch, int x, int y, |
| int w, int h) |
| { |
| int i, j; |
| |
| for (j = y; j < y + h; j++) { |
| for (i = x; i < x + w; i++) { |
| patchpix[j * frame->width + i] = patch->val; |
| } |
| } |
| patch->xmin = MAX (1, x - 1); |
| patch->xmax = MIN (x + w + 1, frame->width - 1); |
| patch->ymin = MAX (1, y - 1); |
| patch->ymax = MIN (y + h + 1, frame->height - 1); |
| patch->count = w * h; |
| |
| } |
| |
| static void |
| patch_grow (Frame * frame, guint8 * patchpix, Patch * patch) |
| { |
| gboolean growmore = FALSE; |
| guint8 *ydata, *udata, *vdata; |
| int i, j; |
| int count = 5; |
| |
| #define MAXDIFF 15 |
| do { |
| for (j = patch->ymin; j < patch->ymax; j++) { |
| ydata = frame->y + frame->ystride * j; |
| udata = frame->u + frame->ustride * (j / 2); |
| vdata = frame->v + frame->vstride * (j / 2); |
| for (i = patch->xmin; i < patch->xmax; i++) { |
| if (patchpix[j * frame->width + i] != 0) |
| continue; |
| |
| if (patchpix[(j + 1) * frame->width + i] == patch->val || |
| patchpix[(j - 1) * frame->width + i] == patch->val || |
| patchpix[j * frame->width + i + 1] == patch->val || |
| patchpix[j * frame->width + i - 1] == patch->val) { |
| int diff = ABS (ydata[i] - patch->y) + |
| ABS (udata[i / 2] - patch->u) + ABS (vdata[i / 2] - patch->v); |
| |
| if (diff < MAXDIFF) { |
| patchpix[j * frame->width + i] = patch->val; |
| patch->xmin = MIN (patch->xmin, MAX (i - 1, 1)); |
| patch->xmax = MAX (patch->xmax, MIN (i + 2, frame->width - 1)); |
| patch->ymin = MIN (patch->ymin, MAX (j - 1, 1)); |
| patch->ymax = MAX (patch->ymax, MIN (j + 2, frame->height - 1)); |
| patch->count++; |
| growmore = TRUE; |
| } |
| } |
| } |
| } |
| for (j = patch->ymax - 1; j >= patch->ymin; j--) { |
| ydata = frame->y + frame->ystride * j; |
| udata = frame->u + frame->ustride * (j / 2); |
| vdata = frame->v + frame->vstride * (j / 2); |
| for (i = patch->xmax - 1; i >= patch->xmin; i--) { |
| if (patchpix[j * frame->width + i] != 0) |
| continue; |
| |
| if (patchpix[(j + 1) * frame->width + i] == patch->val || |
| patchpix[(j - 1) * frame->width + i] == patch->val || |
| patchpix[j * frame->width + i + 1] == patch->val || |
| patchpix[j * frame->width + i - 1] == patch->val) { |
| int diff = ABS (ydata[i] - patch->y) + |
| ABS (udata[i / 2] - patch->u) + ABS (vdata[i / 2] - patch->v); |
| |
| if (diff < MAXDIFF) { |
| patchpix[j * frame->width + i] = patch->val; |
| patch->xmin = MIN (patch->xmin, MAX (i - 1, 1)); |
| patch->xmax = MAX (patch->xmax, MIN (i + 2, frame->width - 1)); |
| patch->ymin = MIN (patch->ymin, MAX (j - 1, 1)); |
| patch->ymax = MAX (patch->ymax, MIN (j + 2, frame->height - 1)); |
| patch->count++; |
| growmore = TRUE; |
| } |
| } |
| } |
| } |
| |
| count--; |
| } while (growmore && count > 0); |
| |
| #if 0 |
| for (j = patch->ymin; j < patch->ymax; j++) { |
| guint8 *data; |
| data = frame->y + frame->ystride * j; |
| for (i = patch->xmin; i < patch->xmax; i++) { |
| if (patchpix[j * frame->width + i] != patch->val) |
| continue; |
| if ((i + j + frame->t) & 0x4) { |
| data[i] = 16; |
| } |
| } |
| } |
| #endif |
| |
| } |
| |
| #if 0 |
| static void |
| find_cluster (Point * points, int n_points, int *result_x, int *result_y) |
| { |
| int dist; |
| int ave_x, ave_y; |
| int i; |
| |
| for (dist = 50; dist >= 10; dist -= 5) { |
| int sum_x, sum_y; |
| int n_valid; |
| |
| sum_x = 0; |
| sum_y = 0; |
| n_valid = 0; |
| for (i = 0; i < n_points; i++) { |
| if (!points[i].valid) |
| continue; |
| sum_x += points[i].x; |
| sum_y += points[i].y; |
| n_valid++; |
| } |
| ave_x = sum_x / n_valid; |
| ave_y = sum_y / n_valid; |
| |
| for (i = 0; i < n_points; i++) { |
| int d; |
| if (!points[i].valid) |
| continue; |
| d = (points[i].x - ave_x) * (points[i].x - ave_x); |
| d += (points[i].y - ave_y) * (points[i].y - ave_y); |
| if (d > dist * dist) |
| points[i].valid = FALSE; |
| } |
| } |
| *result_x = ave_x; |
| *result_y = ave_y; |
| } |
| #endif |
| |
| typedef struct _Matrix Matrix; |
| struct _Matrix |
| { |
| double m[4][4]; |
| }; |
| |
| #if 0 |
| static void |
| dump_4x4 (double a[4][4], double b[4][4]) |
| { |
| int j; |
| int i; |
| |
| for (j = 0; j < 4; j++) { |
| g_print ("[ "); |
| for (i = 0; i < 4; i++) { |
| g_print ("%8.2g", a[i][j]); |
| if (i != 4 - 1) |
| g_print (", "); |
| } |
| g_print ("|"); |
| for (i = 0; i < 4; i++) { |
| g_print ("%8.2g", b[i][j]); |
| if (i != 4 - 1) |
| g_print (", "); |
| } |
| g_print ("]\n"); |
| } |
| g_print ("\n"); |
| |
| } |
| #endif |
| |
| static void |
| invert_matrix (double m[10][10], int n) |
| { |
| int i, j, k; |
| double tmp[10][10] = { {0} }; |
| double x; |
| |
| for (i = 0; i < n; i++) { |
| tmp[i][i] = 1; |
| } |
| |
| for (j = 0; j < n; j++) { |
| for (k = 0; k < n; k++) { |
| if (k == j) |
| continue; |
| |
| x = m[j][k] / m[j][j]; |
| for (i = 0; i < n; i++) { |
| m[i][k] -= x * m[i][j]; |
| tmp[i][k] -= x * tmp[i][j]; |
| } |
| } |
| |
| x = m[j][j]; |
| for (i = 0; i < n; i++) { |
| m[i][j] /= x; |
| tmp[i][j] /= x; |
| } |
| } |
| |
| memcpy (m, tmp, sizeof (tmp)); |
| } |
| |
| static GstFlowReturn |
| gst_patchdetect_transform_ip (GstBaseTransform * trans, GstBuffer * buf) |
| { |
| GstPatchdetect *patchdetect = GST_PATCHDETECT (trans); |
| Frame frame; |
| Point *points; |
| int i, j; |
| int blocks_x, blocks_y; |
| int n_points; |
| int n_patches; |
| Patch *patches; |
| guint8 *patchpix; |
| int vec1_x, vec1_y; |
| int vec2_x, vec2_y; |
| Color detected_colors[24]; |
| gboolean detected = FALSE; |
| |
| frame.y = GST_BUFFER_DATA (buf); |
| frame.ystride = gst_video_format_get_row_stride (patchdetect->format, |
| 0, patchdetect->width); |
| frame.u = |
| frame.y + gst_video_format_get_component_offset (patchdetect->format, 1, |
| patchdetect->width, patchdetect->height); |
| frame.ustride = |
| gst_video_format_get_row_stride (patchdetect->format, 1, |
| patchdetect->width); |
| frame.v = |
| frame.y + gst_video_format_get_component_offset (patchdetect->format, 2, |
| patchdetect->width, patchdetect->height); |
| frame.vstride = |
| gst_video_format_get_row_stride (patchdetect->format, 2, |
| patchdetect->width); |
| frame.width = patchdetect->width; |
| frame.height = patchdetect->height; |
| frame.t = patchdetect->t; |
| patchdetect->t++; |
| |
| blocks_y = (patchdetect->height & (~7)) / 8; |
| blocks_x = (patchdetect->width & (~7)) / 8; |
| |
| patchpix = g_malloc0 (patchdetect->width * patchdetect->height); |
| patches = g_malloc0 (sizeof (Patch) * 256); |
| |
| n_patches = 0; |
| for (j = 0; j < blocks_y; j += 4) { |
| for (i = 0; i < blocks_x; i += 4) { |
| Stats block = { 0 }; |
| |
| get_block_stats (&frame, i * 8, j * 8, &block); |
| |
| patches[n_patches].val = n_patches + 2; |
| if (block.match) { |
| if (patch_check (&frame, patchpix, i * 8, j * 8, 8, 8)) { |
| patch_start (&frame, patchpix, patches + n_patches, i * 8, j * 8, 8, |
| 8); |
| |
| patches[n_patches].y = block.y; |
| patches[n_patches].u = block.u; |
| patches[n_patches].v = block.v; |
| |
| patch_grow (&frame, patchpix, patches + n_patches); |
| n_patches++; |
| g_assert (n_patches < 256); |
| } |
| } |
| } |
| } |
| |
| { |
| int n; |
| |
| for (n = 0; n < n_patches; n++) { |
| Patch *patch = &patches[n]; |
| int xsum; |
| int ysum; |
| |
| if (patch->count > 10000) |
| continue; |
| patch->valid = TRUE; |
| |
| xsum = 0; |
| ysum = 0; |
| for (j = patch->ymin; j < patch->ymax; j++) { |
| for (i = patch->xmin; i < patch->xmax; i++) { |
| if (patchpix[j * frame.width + i] != patch->val) |
| continue; |
| xsum += i; |
| ysum += j; |
| } |
| } |
| |
| patch->cen_x = xsum / patch->count; |
| patch->cen_y = ysum / patch->count; |
| } |
| |
| } |
| |
| points = g_malloc0 (sizeof (Point) * 1000); |
| n_points = 0; |
| |
| for (i = 0; i < n_patches; i++) { |
| for (j = i + 1; j < n_patches; j++) { |
| int dist_x, dist_y; |
| |
| if (i == j) |
| continue; |
| |
| dist_x = patches[i].cen_x - patches[j].cen_x; |
| dist_y = patches[i].cen_y - patches[j].cen_y; |
| |
| if (dist_x < 0) { |
| dist_x = -dist_x; |
| dist_y = -dist_y; |
| } |
| if (ABS (2 * dist_y) < dist_x && dist_x < 100) { |
| points[n_points].x = dist_x; |
| points[n_points].y = dist_y; |
| points[n_points].valid = TRUE; |
| points[n_points].patch1 = i; |
| points[n_points].patch2 = j; |
| n_points++; |
| g_assert (n_points < 1000); |
| } |
| } |
| } |
| |
| { |
| int dist; |
| int ave_x = 0, ave_y = 0; |
| for (dist = 50; dist >= 10; dist -= 5) { |
| int sum_x, sum_y; |
| int n_valid; |
| |
| sum_x = 0; |
| sum_y = 0; |
| n_valid = 0; |
| for (i = 0; i < n_points; i++) { |
| if (!points[i].valid) |
| continue; |
| sum_x += points[i].x; |
| sum_y += points[i].y; |
| n_valid++; |
| } |
| if (n_valid == 0) |
| continue; |
| ave_x = sum_x / n_valid; |
| ave_y = sum_y / n_valid; |
| |
| for (i = 0; i < n_points; i++) { |
| int d; |
| if (!points[i].valid) |
| continue; |
| d = (points[i].x - ave_x) * (points[i].x - ave_x); |
| d += (points[i].y - ave_y) * (points[i].y - ave_y); |
| if (d > dist * dist) |
| points[i].valid = FALSE; |
| } |
| } |
| vec1_x = ave_x; |
| vec1_y = ave_y; |
| } |
| |
| n_points = 0; |
| for (i = 0; i < n_patches; i++) { |
| for (j = i + 1; j < n_patches; j++) { |
| int dist_x, dist_y; |
| |
| if (i == j) |
| continue; |
| |
| dist_x = patches[i].cen_x - patches[j].cen_x; |
| dist_y = patches[i].cen_y - patches[j].cen_y; |
| |
| if (dist_y < 0) { |
| dist_x = -dist_x; |
| dist_y = -dist_y; |
| } |
| if (ABS (2 * dist_x) < dist_y && dist_y < 100) { |
| points[n_points].x = dist_x; |
| points[n_points].y = dist_y; |
| points[n_points].valid = TRUE; |
| points[n_points].patch1 = i; |
| points[n_points].patch2 = j; |
| n_points++; |
| g_assert (n_points < 1000); |
| } |
| } |
| } |
| |
| { |
| int dist; |
| int ave_x = 0, ave_y = 0; |
| for (dist = 50; dist >= 10; dist -= 5) { |
| int sum_x, sum_y; |
| int n_valid; |
| |
| sum_x = 0; |
| sum_y = 0; |
| n_valid = 0; |
| for (i = 0; i < n_points; i++) { |
| if (!points[i].valid) |
| continue; |
| sum_x += points[i].x; |
| sum_y += points[i].y; |
| n_valid++; |
| } |
| if (n_valid == 0) |
| continue; |
| ave_x = sum_x / n_valid; |
| ave_y = sum_y / n_valid; |
| |
| for (i = 0; i < n_points; i++) { |
| int d; |
| if (!points[i].valid) |
| continue; |
| d = (points[i].x - ave_x) * (points[i].x - ave_x); |
| d += (points[i].y - ave_y) * (points[i].y - ave_y); |
| if (d > dist * dist) |
| points[i].valid = FALSE; |
| } |
| } |
| vec2_x = ave_x; |
| vec2_y = ave_y; |
| } |
| |
| #if 0 |
| for (i = 0; i < n_points; i++) { |
| if (!points[i].valid) |
| continue; |
| paint_block (&frame, 4 * points[i].x, 240 + 4 * points[i].y, 16); |
| } |
| #endif |
| #if 0 |
| paint_block (&frame, 360, 240, 16); |
| paint_block (&frame, 360 + vec1_x, 240 + vec1_y, 16); |
| paint_block (&frame, 360 + vec2_x, 240 + vec2_y, 16); |
| #endif |
| |
| { |
| double m00, m01, m10, m11; |
| double det; |
| double v1, v2; |
| double ave_v1 = 0, ave_v2 = 0; |
| |
| det = vec1_x * vec2_y - vec1_y * vec2_x; |
| m00 = vec2_y / det; |
| m01 = -vec2_x / det; |
| m10 = -vec1_y / det; |
| m11 = vec1_x / det; |
| |
| for (i = 0; i < n_patches - 1; i++) { |
| int count = 0; |
| double sum_v1 = 0; |
| double sum_v2 = 0; |
| |
| if (!patches[i].valid) |
| continue; |
| |
| n_points = 0; |
| for (j = i + 1; j < n_patches; j++) { |
| int diff_x = patches[j].cen_x - patches[i].cen_x; |
| int diff_y = patches[j].cen_y - patches[i].cen_y; |
| |
| if (!patches[j].valid) |
| continue; |
| |
| v1 = diff_x * m00 + diff_y * m01; |
| v2 = diff_x * m10 + diff_y * m11; |
| |
| if (v1 > -0.5 && v1 < 5.5 && v2 > -0.5 && v2 < 3.5 && |
| ABS (v1 - rint (v1)) < 0.1 && ABS (v2 - rint (v2)) < 0.1) { |
| sum_v1 += v1 - rint (v1); |
| sum_v2 += v2 - rint (v2); |
| count++; |
| } |
| } |
| ave_v1 = sum_v1 / count; |
| ave_v2 = sum_v2 / count; |
| |
| if (count > 20) { |
| int k; |
| for (j = 0; j < 4; j++) { |
| for (k = 0; k < 6; k++) { |
| Stats block; |
| |
| int xx; |
| int yy; |
| xx = patches[i].cen_x + (ave_v1 + k) * vec1_x + (ave_v2 + |
| j) * vec2_x; |
| yy = patches[i].cen_y + (ave_v1 + k) * vec1_y + (ave_v2 + |
| j) * vec2_y; |
| |
| get_block_stats (&frame, xx - 4, yy - 4, &block); |
| //GST_ERROR("%d %d: %d %d %d", k, j, block.y, block.u, block.v); |
| |
| detected_colors[k + j * 6].y = block.y; |
| detected_colors[k + j * 6].u = block.u; |
| detected_colors[k + j * 6].v = block.v; |
| |
| paint_block (&frame, xx - 4, yy - 4, 16); |
| } |
| } |
| |
| detected = TRUE; |
| |
| #if 0 |
| for (j = i + 1; j < n_patches; j++) { |
| int diff_x = patches[j].cen_x - patches[i].cen_x; |
| int diff_y = patches[j].cen_y - patches[i].cen_y; |
| int xx; |
| int yy; |
| |
| if (!patches[j].valid) |
| continue; |
| |
| v1 = diff_x * m00 + diff_y * m01; |
| v2 = diff_x * m10 + diff_y * m11; |
| |
| if (v1 > -0.5 && v1 < 5.5 && v2 > -0.5 && v2 < 3.5 && |
| ABS (v1 - rint (v1)) < 0.1 && ABS (v2 - rint (v2)) < 0.1) { |
| v1 = rint (v1); |
| v2 = rint (v2); |
| xx = patches[i].cen_x + (ave_v1 + v1) * vec1_x + (ave_v2 + |
| v2) * vec2_x; |
| yy = patches[i].cen_y + (ave_v1 + v1) * vec1_y + (ave_v2 + |
| v2) * vec2_y; |
| |
| paint_block (&frame, patches[j].cen_x, patches[j].cen_y, 128); |
| paint_block (&frame, xx, yy, 16); |
| } |
| } |
| paint_block (&frame, patches[i].cen_x, patches[i].cen_y, 240); |
| #endif |
| break; |
| } |
| } |
| } |
| |
| #define N 10 |
| if (detected) { |
| int i, j, k; |
| int n = N; |
| double diff = 0; |
| double matrix[10][10] = { {0} }; |
| double vy[10] = { 0 }; |
| double vu[10] = { 0 }; |
| double vv[10] = { 0 }; |
| double *by = patchdetect->by; |
| double *bu = patchdetect->bu; |
| double *bv = patchdetect->bv; |
| double flip_diff = 0; |
| |
| for (i = 0; i < 24; i++) { |
| diff += ABS (detected_colors[i].y - patch_colors[i].y); |
| diff += ABS (detected_colors[i].u - patch_colors[i].u); |
| diff += ABS (detected_colors[i].v - patch_colors[i].v); |
| |
| flip_diff += ABS (detected_colors[23 - i].y - patch_colors[i].y); |
| flip_diff += ABS (detected_colors[23 - i].u - patch_colors[i].u); |
| flip_diff += ABS (detected_colors[23 - i].v - patch_colors[i].v); |
| } |
| GST_ERROR ("uncorrected error %g (flipped %g)", diff / 24.0, |
| flip_diff / 24.0); |
| if (flip_diff < diff) { |
| for (i = 0; i < 12; i++) { |
| Color tmp; |
| tmp = detected_colors[i]; |
| detected_colors[i] = detected_colors[23 - i]; |
| detected_colors[23 - i] = tmp; |
| } |
| } |
| |
| for (i = 0; i < 24; i++) { |
| int dy = detected_colors[i].y - patch_colors[i].y; |
| int du = detected_colors[i].u - patch_colors[i].u; |
| int dv = detected_colors[i].v - patch_colors[i].v; |
| int py = detected_colors[i].y - 128; |
| int pu = detected_colors[i].u - 128; |
| int pv = detected_colors[i].v - 128; |
| int w = (i < 18) ? 1 : 2; |
| double z[10]; |
| |
| diff += ABS (dy) + ABS (du) + ABS (dv); |
| |
| z[0] = 1; |
| z[1] = py; |
| z[2] = pu; |
| z[3] = pv; |
| z[4] = py * py; |
| z[5] = py * pu; |
| z[6] = py * pv; |
| z[7] = pu * pu; |
| z[8] = pu * pv; |
| z[9] = pv * pv; |
| |
| for (j = 0; j < n; j++) { |
| for (k = 0; k < n; k++) { |
| matrix[j][k] += w * z[j] * z[k]; |
| } |
| |
| vy[j] += w * dy * z[j]; |
| vu[j] += w * du * z[j]; |
| vv[j] += w * dv * z[j]; |
| } |
| } |
| |
| invert_matrix (matrix, n); |
| |
| for (i = 0; i < n; i++) { |
| by[i] = 0; |
| bu[i] = 0; |
| bv[i] = 0; |
| for (j = 0; j < n; j++) { |
| by[i] += matrix[i][j] * vy[j]; |
| bu[i] += matrix[i][j] * vu[j]; |
| bv[i] += matrix[i][j] * vv[j]; |
| } |
| } |
| |
| //GST_ERROR("a %g %g %g b %g %g %g", ay, au, av, by, bu, bv); |
| |
| diff = 0; |
| for (i = 0; i < 24; i++) { |
| double cy, cu, cv; |
| double z[10]; |
| int py = detected_colors[i].y - 128; |
| int pu = detected_colors[i].u - 128; |
| int pv = detected_colors[i].v - 128; |
| |
| z[0] = 1; |
| z[1] = py; |
| z[2] = pu; |
| z[3] = pv; |
| z[4] = py * py; |
| z[5] = py * pu; |
| z[6] = py * pv; |
| z[7] = pu * pu; |
| z[8] = pu * pv; |
| z[9] = pv * pv; |
| |
| cy = 0; |
| cu = 0; |
| cv = 0; |
| for (j = 0; j < n; j++) { |
| cy += by[j] * z[j]; |
| cu += bu[j] * z[j]; |
| cv += bv[j] * z[j]; |
| } |
| |
| diff += fabs (patch_colors[i].y - (128 + py - cy)); |
| diff += fabs (patch_colors[i].u - (128 + pu - cu)); |
| diff += fabs (patch_colors[i].v - (128 + pv - cv)); |
| } |
| GST_ERROR ("average error %g", diff / 24.0); |
| patchdetect->valid = 3000; |
| } |
| |
| if (patchdetect->valid > 0) { |
| int n = N; |
| guint8 *u1, *u2; |
| guint8 *v1, *v2; |
| double *by = patchdetect->by; |
| double *bu = patchdetect->bu; |
| double *bv = patchdetect->bv; |
| |
| patchdetect->valid--; |
| u1 = g_malloc (frame.width); |
| u2 = g_malloc (frame.width); |
| v1 = g_malloc (frame.width); |
| v2 = g_malloc (frame.width); |
| |
| for (j = 0; j < frame.height; j += 2) { |
| for (i = 0; i < frame.width / 2; i++) { |
| u1[2 * i + 0] = frame.u[(j / 2) * frame.ustride + i]; |
| u1[2 * i + 1] = u1[2 * i + 0]; |
| u2[2 * i + 0] = u1[2 * i + 0]; |
| u2[2 * i + 1] = u1[2 * i + 0]; |
| v1[2 * i + 0] = frame.v[(j / 2) * frame.vstride + i]; |
| v1[2 * i + 1] = v1[2 * i + 0]; |
| v2[2 * i + 0] = v1[2 * i + 0]; |
| v2[2 * i + 1] = v1[2 * i + 0]; |
| } |
| for (i = 0; i < frame.width; i++) { |
| int k; |
| double z[10]; |
| double cy, cu, cv; |
| int y, u, v; |
| int py, pu, pv; |
| |
| y = frame.y[(j + 0) * frame.ystride + i]; |
| u = u1[i]; |
| v = v1[i]; |
| |
| py = y - 128; |
| pu = u - 128; |
| pv = v - 128; |
| |
| z[0] = 1; |
| z[1] = py; |
| z[2] = pu; |
| z[3] = pv; |
| z[4] = py * py; |
| z[5] = py * pu; |
| z[6] = py * pv; |
| z[7] = pu * pu; |
| z[8] = pu * pv; |
| z[9] = pv * pv; |
| |
| cy = 0; |
| cu = 0; |
| cv = 0; |
| for (k = 0; k < n; k++) { |
| cy += by[k] * z[k]; |
| cu += bu[k] * z[k]; |
| cv += bv[k] * z[k]; |
| } |
| |
| frame.y[(j + 0) * frame.ystride + i] = CLAMP (rint (y - cy), 0, 255); |
| u1[i] = CLAMP (rint (u - cu), 0, 255); |
| v1[i] = CLAMP (rint (v - cv), 0, 255); |
| |
| y = frame.y[(j + 1) * frame.ystride + i]; |
| u = u2[i]; |
| v = v2[i]; |
| |
| py = y - 128; |
| pu = u - 128; |
| pv = v - 128; |
| |
| z[0] = 1; |
| z[1] = py; |
| z[2] = pu; |
| z[3] = pv; |
| z[4] = py * py; |
| z[5] = py * pu; |
| z[6] = py * pv; |
| z[7] = pu * pu; |
| z[8] = pu * pv; |
| z[9] = pv * pv; |
| |
| cy = 0; |
| cu = 0; |
| cv = 0; |
| for (k = 0; k < n; k++) { |
| cy += by[k] * z[k]; |
| cu += bu[k] * z[k]; |
| cv += bv[k] * z[k]; |
| } |
| |
| frame.y[(j + 1) * frame.ystride + i] = CLAMP (rint (y - cy), 0, 255); |
| u2[i] = CLAMP (rint (u - cu), 0, 255); |
| v2[i] = CLAMP (rint (v - cv), 0, 255); |
| } |
| for (i = 0; i < frame.width / 2; i++) { |
| frame.u[(j / 2) * frame.ustride + i] = (u1[2 * i + 0] + |
| u1[2 * i + 1] + u2[2 * i + 0] + u2[2 * i + 1] + 2) >> 2; |
| frame.v[(j / 2) * frame.vstride + i] = (v1[2 * i + 0] + |
| v1[2 * i + 1] + v2[2 * i + 0] + v2[2 * i + 1] + 2) >> 2; |
| } |
| } |
| |
| g_free (u1); |
| g_free (u2); |
| g_free (v1); |
| g_free (v2); |
| } |
| |
| g_free (points); |
| g_free (patches); |
| g_free (patchpix); |
| |
| return GST_FLOW_OK; |
| } |
| |
| static gboolean |
| gst_patchdetect_src_event (GstBaseTransform * trans, GstEvent * event) |
| { |
| |
| return TRUE; |
| } |
| |
| static gboolean |
| plugin_init (GstPlugin * plugin) |
| { |
| |
| gst_element_register (plugin, "patchdetect", GST_RANK_NONE, |
| gst_patchdetect_get_type ()); |
| |
| return TRUE; |
| } |
| |
| GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, |
| GST_VERSION_MINOR, |
| patchdetect, |
| "patchdetect element", |
| plugin_init, VERSION, "LGPL", PACKAGE_NAME, GST_PACKAGE_ORIGIN) |