[shotwell/wip/phako/enhanced-faces: 22/136] First working version of face extraction into FaceLocationTable
- From: Jens Georg <jensgeorg src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [shotwell/wip/phako/enhanced-faces: 22/136] First working version of face extraction into FaceLocationTable
- Date: Thu, 11 Oct 2018 09:55:55 +0000 (UTC)
commit 76a130d28615de67646b1163c3b9c1ddb68df9ba
Author: NarendraMA <narendra_m_a yahoo com>
Date: Sat Jul 7 18:22:32 2018 +0530
First working version of face extraction into FaceLocationTable
src/Commands.vala | 22 ++++++-----
src/Photo.vala | 2 +-
src/db/DatabaseTable.vala | 2 +-
src/db/Db.vala | 15 ++++++++
src/db/FaceLocationTable.vala | 57 +++++++++++++++++++++++++++--
src/faces/Face.vala | 2 +-
src/faces/FaceLocation.vala | 42 ++++++++++++++-------
src/faces/FaceShape.vala | 12 ++++++
src/faces/FacesTool.vala | 30 +++++++++++----
thumbnailer/shotwell-video-thumbnailer.vala | 12 +++---
10 files changed, 153 insertions(+), 43 deletions(-)
---
diff --git a/src/Commands.vala b/src/Commands.vala
index 6924f826..a0d3766c 100644
--- a/src/Commands.vala
+++ b/src/Commands.vala
@@ -2542,7 +2542,8 @@ public class RemoveFacesFromPhotosCommand : SimpleProxyableCommand {
face.attach_many(map_source_geometry.keys);
foreach (Gee.Map.Entry<MediaSource, string> entry in map_source_geometry.entries)
- FaceLocation.create(face.get_face_id(), ((Photo) entry.key).get_photo_id(), entry.value);
+ FaceLocation.create(face.get_face_id(), ((Photo) entry.key).get_photo_id(),
+ { entry.value, null });
}
private void on_source_destroyed(DataSource source) {
@@ -2608,7 +2609,8 @@ public class DeleteFaceCommand : SimpleProxyableCommand {
Face face = (Face) source;
face.attach(photo);
- FaceLocation.create(face.get_face_id(), entry.key, entry.value);
+ FaceLocation.create(face.get_face_id(), entry.key,
+ { entry.value, null });
}
}
}
@@ -2618,10 +2620,10 @@ public class ModifyFacesCommand : SingleDataSourceCommand {
private MediaSource media;
private Gee.ArrayList<SourceProxy> to_add = new Gee.ArrayList<SourceProxy>();
private Gee.ArrayList<SourceProxy> to_remove = new Gee.ArrayList<SourceProxy>();
- private Gee.Map<SourceProxy, string> to_update = new Gee.HashMap<SourceProxy, string>();
- private Gee.Map<SourceProxy, string> geometries = new Gee.HashMap<SourceProxy, string>();
+ private Gee.Map<SourceProxy, FaceLocationData?> to_update = new Gee.HashMap<SourceProxy,
FaceLocationData?>();
+ private Gee.Map<SourceProxy, FaceLocationData?> geometries = new Gee.HashMap<SourceProxy,
FaceLocationData?>();
- public ModifyFacesCommand(MediaSource media, Gee.Map<Face, string> new_face_list) {
+ public ModifyFacesCommand(MediaSource media, Gee.Map<Face, FaceLocationData?> new_face_list) {
base (media, Resources.MODIFY_FACES_LABEL, "");
this.media = media;
@@ -2640,13 +2642,13 @@ public class ModifyFacesCommand : SingleDataSourceCommand {
FaceLocation.get_face_location(face.get_face_id(), ((Photo) media).get_photo_id());
assert(face_location != null);
- geometries.set(proxy, face_location.get_serialized_geometry());
+ geometries.set(proxy, face_location.get_face_data());
}
}
}
// Add any face that's in the new list but not the original
- foreach (Gee.Map.Entry<Face, string> entry in new_face_list.entries) {
+ foreach (Gee.Map.Entry<Face, FaceLocationData?> entry in new_face_list.entries) {
if (original_faces == null || !original_faces.contains(entry.key)) {
SourceProxy proxy = entry.key.get_proxy();
@@ -2662,13 +2664,13 @@ public class ModifyFacesCommand : SingleDataSourceCommand {
assert(face_location != null);
string old_geometry = face_location.get_serialized_geometry();
- if (old_geometry != entry.value) {
+ if (old_geometry != entry.value.geometry) {
SourceProxy proxy = entry.key.get_proxy();
to_update.set(proxy, entry.value);
proxy.broken.connect(on_proxy_broken);
- geometries.set(proxy, old_geometry);
+ geometries.set(proxy, face_location.get_face_data());
}
}
}
@@ -2695,7 +2697,7 @@ public class ModifyFacesCommand : SingleDataSourceCommand {
foreach (SourceProxy proxy in to_remove)
((Face) proxy.get_source()).detach(media);
- foreach (Gee.Map.Entry<SourceProxy, string> entry in to_update.entries) {
+ foreach (Gee.Map.Entry<SourceProxy, FaceLocationData?> entry in to_update.entries) {
Face face = (Face) entry.key.get_source();
FaceLocation.create(face.get_face_id(), ((Photo) media).get_photo_id(), entry.value);
}
diff --git a/src/Photo.vala b/src/Photo.vala
index c317714e..fc541003 100644
--- a/src/Photo.vala
+++ b/src/Photo.vala
@@ -5226,7 +5226,7 @@ public class LibraryPhoto : Photo, Flaggable, Monitorable {
if (location != null) {
face.attach(dupe);
FaceLocation.create(face.get_face_id(), dupe.get_photo_id(),
- location.get_serialized_geometry());
+ location.get_face_data());
}
}
}
diff --git a/src/db/DatabaseTable.vala b/src/db/DatabaseTable.vala
index 5ec5be12..293d7546 100644
--- a/src/db/DatabaseTable.vala
+++ b/src/db/DatabaseTable.vala
@@ -21,7 +21,7 @@ public abstract class DatabaseTable {
* tables are created on demand and tables and columns are easily ignored when already present.
* However, the change should be noted in upgrade_database() as a comment.
***/
- public const int SCHEMA_VERSION = 20;
+ public const int SCHEMA_VERSION = 21;
protected static Sqlite.Database db;
diff --git a/src/db/Db.vala b/src/db/Db.vala
index 3eca8cee..ba565b4b 100644
--- a/src/db/Db.vala
+++ b/src/db/Db.vala
@@ -349,6 +349,21 @@ private VerifyResult upgrade_database(int input_version) {
//
version = 20;
+
+#if ENABLE_FACES
+ //
+ // Version 21:
+ // * Added face pixels column to FaceLocationTable
+ //
+
+ if (!DatabaseTable.has_column("FaceLocationTable", "pix")) {
+ message("upgrade_database: adding pix column to FaceLocationTable");
+ if (!DatabaseTable.add_column("FaceLocationTable", "pix", "BLOB"))
+ return VerifyResult.UPGRADE_ERROR;
+ }
+
+ version = 21;
+#endif
//
// Finalize the upgrade process
diff --git a/src/db/FaceLocationTable.vala b/src/db/FaceLocationTable.vala
index 14fef4c7..f8132615 100644
--- a/src/db/FaceLocationTable.vala
+++ b/src/db/FaceLocationTable.vala
@@ -29,6 +29,7 @@ public class FaceLocationRow {
public FaceID face_id;
public PhotoID photo_id;
public string geometry;
+ public uint8[] pix;
}
public class FaceLocationTable : DatabaseTable {
@@ -44,7 +45,8 @@ public class FaceLocationTable : DatabaseTable {
+ "id INTEGER NOT NULL PRIMARY KEY, "
+ "face_id INTEGER NOT NULL, "
+ "photo_id INTEGER NOT NULL, "
- + "geometry TEXT"
+ + "geometry TEXT, "
+ + "pix BLOB"
+ ")", -1, out stmt);
assert(res == Sqlite.OK);
@@ -60,10 +62,10 @@ public class FaceLocationTable : DatabaseTable {
return instance;
}
- public FaceLocationRow add(FaceID face_id, PhotoID photo_id, string geometry) throws DatabaseError {
+ public FaceLocationRow add(FaceID face_id, PhotoID photo_id, string geometry, uint8[]? pix = null)
throws DatabaseError {
Sqlite.Statement stmt;
int res = db.prepare_v2(
- "INSERT INTO FaceLocationTable (face_id, photo_id, geometry) VALUES (?, ?, ?)",
+ "INSERT INTO FaceLocationTable (face_id, photo_id, geometry, pix) VALUES (?, ?, ?, ?)",
-1, out stmt);
assert(res == Sqlite.OK);
@@ -73,6 +75,13 @@ public class FaceLocationTable : DatabaseTable {
assert(res == Sqlite.OK);
res = stmt.bind_text(3, geometry);
assert(res == Sqlite.OK);
+ message("face table pix len %d", pix.length);
+ if (pix == null) {
+ res = stmt.bind_blob(4, null, 0);
+ } else {
+ res = stmt.bind_blob(4, pix, pix.length);
+ }
+ assert(res == Sqlite.OK);
res = stmt.step();
if (res != Sqlite.DONE)
@@ -83,6 +92,7 @@ public class FaceLocationTable : DatabaseTable {
row.face_id = face_id;
row.photo_id = photo_id;
row.geometry = geometry;
+ row.pix = pix;
return row;
}
@@ -165,6 +175,28 @@ public class FaceLocationTable : DatabaseTable {
return stmt.column_text(0);
}
+ public uint8[]? get_face_source_pix(Face face, MediaSource source)
+ throws DatabaseError {
+ Sqlite.Statement stmt;
+ int res = db.prepare_v2(
+ "SELECT pix FROM FaceLocationTable WHERE face_id=? AND photo_id=?",
+ -1, out stmt);
+ assert(res == Sqlite.OK);
+
+ res = stmt.bind_int64(1, face.get_instance_id());
+ assert(res == Sqlite.OK);
+ res = stmt.bind_int64(2, ((Photo) source).get_instance_id());
+ assert(res == Sqlite.OK);
+
+ res = stmt.step();
+ if (res == Sqlite.DONE)
+ return null;
+ else if (res != Sqlite.ROW)
+ throw_error("FaceLocationTable.get_face_source_pix", res);
+
+ return (uint8[])stmt.column_blob(0);
+ }
+
public void remove_face_from_source(FaceID face_id, PhotoID photo_id) throws DatabaseError {
Sqlite.Statement stmt;
int res = db.prepare_v2(
@@ -197,6 +229,25 @@ public class FaceLocationTable : DatabaseTable {
if (res != Sqlite.DONE)
throw_error("FaceLocationTable.update_face_location_serialized_geometry", res);
}
+
+ public void update_face_location_face_data(FaceLocation face_location)
+ throws DatabaseError {
+ Sqlite.Statement stmt;
+ int res = db.prepare_v2("UPDATE FaceLocationTable SET geometry=?, pix=? WHERE id=?", -1, out stmt);
+ assert(res == Sqlite.OK);
+
+ FaceLocationData face_data = face_location.get_face_data();
+ res = stmt.bind_text(1, face_data.geometry);
+ assert(res == Sqlite.OK);
+ res = stmt.bind_blob(2, face_data.pixbuf, face_data.pixbuf.length);
+ assert(res == Sqlite.OK);
+ res = stmt.bind_int64(3, face_location.get_face_location_id().id);
+ assert(res == Sqlite.OK);
+
+ res = stmt.step();
+ if (res != Sqlite.DONE)
+ throw_error("FaceLocationTable.update_face_location_serialized_geometry", res);
+ }
}
#endif
diff --git a/src/faces/Face.vala b/src/faces/Face.vala
index 722e1727..2443df03 100644
--- a/src/faces/Face.vala
+++ b/src/faces/Face.vala
@@ -417,7 +417,7 @@ public class Face : DataSource, ContainerSource, Proxyable, Indexable {
return face;
}
-
+
// Utility function to cleanup a face name that comes from user input and prepare it for use
// in the system and storage in the database. Returns null if the name is unacceptable.
public static string? prep_face_name(string name) {
diff --git a/src/faces/FaceLocation.vala b/src/faces/FaceLocation.vala
index cc5c4cf3..fce8e99e 100644
--- a/src/faces/FaceLocation.vala
+++ b/src/faces/FaceLocation.vala
@@ -6,6 +6,12 @@
#if ENABLE_FACES
+// Encapsulate geometry and pixels of a Face
+public struct FaceLocationData {
+ public string geometry;
+ public uint8[] pixbuf;
+}
+
public class FaceLocation : Object {
private static Gee.Map<FaceID?, Gee.Map<PhotoID?, FaceLocation>> face_photos_map;
@@ -14,17 +20,17 @@ public class FaceLocation : Object {
private FaceLocationID face_location_id;
private FaceID face_id;
private PhotoID photo_id;
- private string geometry;
-
+ private FaceLocationData face_data;
+
private FaceLocation(FaceLocationID face_location_id, FaceID face_id, PhotoID photo_id,
- string geometry) {
+ FaceLocationData face_data) {
this.face_location_id = face_location_id;
this.face_id = face_id;
this.photo_id = photo_id;
- this.geometry = geometry;
+ this.face_data = face_data;
}
- public static FaceLocation create(FaceID face_id, PhotoID photo_id, string geometry) {
+ public static FaceLocation create(FaceID face_id, PhotoID photo_id, FaceLocationData face_data) {
FaceLocation face_location = null;
// Test if that FaceLocation already exists (that face in that photo) ...
@@ -35,12 +41,11 @@ public class FaceLocation : Object {
face_location = faces_map.get(face_id);
- if (face_location.get_serialized_geometry() != geometry) {
- face_location.set_serialized_geometry(geometry);
+ if (face_location.get_serialized_geometry() != face_data.geometry) {
+ face_location.set_face_data(face_data);
try {
- FaceLocationTable.get_instance().update_face_location_serialized_geometry(
- face_location);
+ FaceLocationTable.get_instance().update_face_location_face_data(face_location);
} catch (DatabaseError err) {
AppWindow.database_error(err);
}
@@ -53,7 +58,7 @@ public class FaceLocation : Object {
try {
face_location =
FaceLocation.add_from_row(
- FaceLocationTable.get_instance().add(face_id, photo_id, geometry));
+ FaceLocationTable.get_instance().add(face_id, photo_id, face_data.geometry,
face_data.pixbuf));
} catch (DatabaseError err) {
AppWindow.database_error(err);
}
@@ -86,7 +91,8 @@ public class FaceLocation : Object {
public static FaceLocation add_from_row(FaceLocationRow row) {
FaceLocation face_location =
- new FaceLocation(row.face_location_id, row.face_id, row.photo_id, row.geometry);
+ new FaceLocation(row.face_location_id, row.face_id, row.photo_id,
+ { row.geometry, row.pix });
Gee.Map<PhotoID?, FaceLocation> photos_map = face_photos_map.get(row.face_id);
if (photos_map == null) {photos_map = new Gee.HashMap<PhotoID?, FaceLocation>
@@ -198,11 +204,19 @@ public class FaceLocation : Object {
}
public string get_serialized_geometry() {
- return geometry;
+ return face_data.geometry;
}
-
+/*
private void set_serialized_geometry(string geometry) {
- this.geometry = geometry;
+ this.face_data.geometry = geometry;
+ }
+*/
+ public FaceLocationData get_face_data() {
+ return face_data;
+ }
+
+ private void set_face_data(FaceLocationData face_data) {
+ this.face_data = face_data;
}
}
diff --git a/src/faces/FaceShape.vala b/src/faces/FaceShape.vala
index c14b43b3..0afc5a72 100644
--- a/src/faces/FaceShape.vala
+++ b/src/faces/FaceShape.vala
@@ -172,6 +172,7 @@ public abstract class FaceShape : Object {
public abstract bool cursor_is_over(int x, int y);
public abstract bool equals(FaceShape face_shape);
public abstract double get_distance(int x, int y);
+ public abstract Gdk.Pixbuf? get_pixbuf();
protected abstract void paint();
protected abstract void erase();
@@ -191,6 +192,7 @@ public class FaceRectangle : FaceShape {
private Cairo.Context thin_white_ctx = null;
private int last_grab_x = -1;
private int last_grab_y = -1;
+ private Gdk.Pixbuf? face_pix;
public FaceRectangle(EditingTools.PhotoCanvas canvas, int x, int y,
int half_width = NULL_SIZE, int half_height = NULL_SIZE) {
@@ -215,6 +217,12 @@ public class FaceRectangle : FaceShape {
box = Box(x - half_width, y - half_height, right, bottom);
}
+
+ Gdk.Pixbuf original = canvas.get_scaled_pixbuf();
+ message("pixbuf get %d, %d, %d, %d of %d, %d", box.left, box.top,
+ box.get_width(), box.get_height(), original.width, original.height);
+ face_pix = new Gdk.Pixbuf.subpixbuf(original, box.left, box.top,
+ box.get_width(), box.get_height());
}
~FaceRectangle() {
@@ -778,6 +786,10 @@ public class FaceRectangle : FaceShape {
return Math.sqrt((center_x - x) * (center_x - x) + (center_y - y) * (center_y - y));
}
+
+ public override Gdk.Pixbuf? get_pixbuf() {
+ return face_pix;
+ }
}
#endif
diff --git a/src/faces/FacesTool.vala b/src/faces/FacesTool.vala
index 00a8b7e0..2c5faf6a 100644
--- a/src/faces/FacesTool.vala
+++ b/src/faces/FacesTool.vala
@@ -314,14 +314,16 @@ public class FacesTool : EditingTools.EditingTool {
private class FaceDetectionJob : BackgroundJob {
private Gee.Queue<string> faces = null;
private string image_path;
+ private float scale;
public string? spawnError;
- public FaceDetectionJob(FacesToolWindow owner, string image_path,
+ public FaceDetectionJob(FacesToolWindow owner, string image_path, float scale,
CompletionCallback completion_callback, Cancellable cancellable,
CancellationCallback cancellation_callback) {
base(owner, completion_callback, cancellable, cancellation_callback);
this.image_path = image_path;
+ this.scale = scale;
}
public override void execute() {
@@ -331,7 +333,8 @@ public class FacesTool : EditingTools.EditingTool {
}
FaceRect[] rects;
try {
- rects = FaceDetect.interface.detect_faces(image_path,
AppDirs.get_haarcascade_file().get_path(), 1.3);
+ rects = FaceDetect.interface.detect_faces(image_path,
+ AppDirs.get_haarcascade_file().get_path(), scale);
} catch(Error e) {
spawnError = "DBus error: " + e.message + "!\n";
return;
@@ -416,6 +419,7 @@ public class FacesTool : EditingTools.EditingTool {
private Workers workers;
private FaceShape editing_face_shape = null;
private FacesToolWindow faces_tool_window = null;
+ private const int FACE_DETECT_MAX_WIDTH = 1200;
private FacesTool() {
base("FacesTool");
@@ -468,9 +472,12 @@ public class FacesTool : EditingTools.EditingTool {
face_detection_cancellable = new Cancellable();
workers = new Workers(1, false);
+ Dimensions dimensions = canvas.get_photo().get_dimensions();
+ float scale_factor = (float)dimensions.width / FACE_DETECT_MAX_WIDTH;
face_detection = new FaceDetectionJob(faces_tool_window,
- canvas.get_photo().get_file().get_path(), on_faces_detected,
- face_detection_cancellable, on_detection_cancelled);
+ canvas.get_photo().get_file().get_path(), scale_factor,
+ on_faces_detected,
+ face_detection_cancellable, on_detection_cancelled);
bind_window_handlers();
@@ -750,14 +757,23 @@ public class FacesTool : EditingTools.EditingTool {
if (face_shapes == null)
return;
- Gee.Map<Face, string> new_faces = new Gee.HashMap<Face, string>();
+ Gee.Map<Face, FaceLocationData?> new_faces = new Gee.HashMap<Face, FaceLocationData?>();
foreach (FaceShape face_shape in face_shapes.values) {
if (!face_shape.get_known())
continue;
Face new_face = Face.for_name(face_shape.get_name());
-
- new_faces.set(new_face, face_shape.serialize());
+ uint8[] face_pix;
+ try {
+ face_shape.get_pixbuf().save_to_buffer(out face_pix, "png");
+ message("face pix length %d", face_pix.length);
+ } catch (Error e) {
+ AppWindow.error_message("Cannot get pixbuf for image\n");
+ }
+ FaceLocationData face_data = {
+ face_shape.serialize(), face_pix
+ };
+ new_faces.set(new_face, face_data);
}
ModifyFacesCommand command = new ModifyFacesCommand(canvas.get_photo(), new_faces);
diff --git a/thumbnailer/shotwell-video-thumbnailer.vala b/thumbnailer/shotwell-video-thumbnailer.vala
index 2b381a6a..94e24515 100644
--- a/thumbnailer/shotwell-video-thumbnailer.vala
+++ b/thumbnailer/shotwell-video-thumbnailer.vala
@@ -54,10 +54,10 @@ class ShotwellThumbnailer {
// Set to PAUSED to make the first frame arrive in the sink.
ret = pipeline.set_state(Gst.State.PAUSED);
if (ret == Gst.StateChangeReturn.FAILURE) {
- stderr.printf("Failed to play the file: couldn't set state\n");
+ debug("Failed to play the file: couldn't set state\n");
return 3;
} else if (ret == Gst.StateChangeReturn.NO_PREROLL) {
- stderr.printf("Live sources not supported yet.\n");
+ debug("Live sources not supported yet.\n");
return 4;
}
@@ -66,13 +66,13 @@ class ShotwellThumbnailer {
// better way is to run a mainloop and catch errors there.
ret = pipeline.get_state(null, null, 5 * Gst.SECOND);
if (ret == Gst.StateChangeReturn.FAILURE) {
- stderr.printf("Failed to play the file: couldn't get state.\n");
+ debug("Failed to play the file: couldn't get state.\n");
return 3;
}
/* get the duration */
if (!pipeline.query_duration (Gst.Format.TIME, out duration)) {
- stderr.printf("Failed to query file for duration\n");
+ debug("Failed to query file for duration\n");
return 3;
}
@@ -86,7 +86,7 @@ class ShotwellThumbnailer {
ret = pipeline.get_state(null, null, 5 * Gst.SECOND);
if (ret == Gst.StateChangeReturn.FAILURE) {
- stderr.printf("Failed to play the file: couldn't get state.\n");
+ debug("Failed to play the file: couldn't get state.\n");
return 3;
}
@@ -100,7 +100,7 @@ class ShotwellThumbnailer {
pipeline.set_state(Gst.State.NULL);
} catch (Error e) {
- stderr.printf(e.message);
+ debug(e.message);
return 2;
}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]