Skip to content

Commit 9870ddb

Browse files
committed
face_recognition.lua script face some stability and performance issues on an i5-6600, 32 GB and GTX 1060 6GB:
1. Taking to much time – almost a day for 3000 images 2. Crashing due to lines malformation in facerecognition.txt – some times face_recognition Python script concatenates two output lines in just once, what is not expected by face_recognition.lua script 3. Crashing the whole system in Linux – I keep receiving “bash: fork: resource temporarily unavailable” trying to process a too big (starting from some hundreds) image list. Some other applications starts to crash at this point. It seems related to the attach tag call because commenting this instruction (for debugging purposes) eliminates the crashes. After debugging the Lua script and checking Lua and SQL darktable out logs, I decided to make the following changes: 1. Remove an inner loop in the processing results step – helps preventing issue 1 2. Remove a loop in the export step – helps preventing issue 1 3. Remove duplicate tags (when there are more more than a reference face for each person) while facerecognition.txt is read – helps preventing issue 1 and 3 4. Make a sanity check with processing images, skip any malformation data – helps preventing issue 2 5. Reduces the sqlite access by caching tags that where already created for a previous image – helps preventing issue 3 Those changes allowed may thousands (more than 30K) of images to be processed in half a day instead of several days.
1 parent a582f02 commit 9870ddb

File tree

1 file changed

+73
-67
lines changed

1 file changed

+73
-67
lines changed

contrib/face_recognition.lua

Lines changed: 73 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ du.check_min_api_version("5.0.0", "face_recognition")
6363
gettext.bindtextdomain("face_recognition", dt.configuration.config_dir.."/lua/locale/")
6464

6565
local function _(msgid)
66-
return gettext.dgettext("face_recognition", msgid)
66+
return gettext.dgettext("face_recognition", msgid)
6767
end
6868

6969
-- preferences
@@ -92,8 +92,10 @@ local function build_image_table(images)
9292
end
9393

9494
for _,img in ipairs(images) do
95-
image_table[img] = tmp_dir .. df.get_basename(img.filename) .. file_extension
96-
cnt = cnt + 1
95+
if img ~= nil then
96+
image_table[tmp_dir .. df.get_basename(img.filename) .. file_extension] = img
97+
cnt = cnt + 1
98+
end
9799
end
98100

99101
return image_table, cnt
@@ -103,17 +105,12 @@ local function stop_job(job)
103105
job.valid = false
104106
end
105107

106-
local function do_export(img_tbl)
108+
local function do_export(img_tbl, images)
107109
local exporter = nil
108110
local upsize = false
109-
local upscale = false
110111
local ff = fc.export_format.value
111112
local height = dt.preferences.read(MODULE, "max_height", "integer")
112113
local width = dt.preferences.read(MODULE, "max_width", "integer")
113-
local images = 0
114-
for k,v in pairs(img_tbl) do
115-
images = images + 1
116-
end
117114

118115
-- get the export format parameters
119116
if string.match(ff, "JPEG") then
@@ -134,7 +131,7 @@ local function do_export(img_tbl)
134131
local exp_cnt = 0
135132
local percent_step = 1.0 / images
136133
job.percent = 0.0
137-
for img,export in pairs(img_tbl) do
134+
for export,img in pairs(img_tbl) do
138135
exp_cnt = exp_cnt + 1
139136
dt.print(string.format(_("Exporting image %i of %i images"), exp_cnt, images))
140137
exporter:write_image(img, export, upsize)
@@ -188,7 +185,7 @@ local function ignoreByTag (image, ignoreTags)
188185
end
189186
end
190187
end
191-
188+
192189
return ignoreImage
193190
end
194191

@@ -221,25 +218,25 @@ local function face_recognition ()
221218
if nrCores < 1 then
222219
nrCores = -1
223220
end
224-
221+
225222
-- Split ignore tags (if any)
226223
ignoreTags = {}
227224
for tag in string.gmatch(ignoreTagString, '([^,]+)') do
228225
table.insert (ignoreTags, tag)
229226
dt.print_log ("Face recognition: Ignore tag: " .. tag)
230227
end
231-
228+
232229
-- list of exported images
233230

234231
local image_table, cnt = build_image_table(dt.gui.action_images)
235232

236233
if cnt > 0 then
237-
local success = do_export(image_table)
234+
local success = do_export(image_table, cnt)
238235
if success then
239236
-- do the face recognition
240237
local img_list = {}
241238

242-
for img,v in pairs(image_table) do
239+
for v,_ in pairs(image_table) do
243240
table.insert (img_list, v)
244241
end
245242

@@ -248,7 +245,7 @@ local function face_recognition ()
248245
dt.print_log ("Face recognition: Path to unknown images: " .. path)
249246
os.setlocale("C")
250247
local tolerance = dt.preferences.read(MODULE, "tolerance", "float")
251-
248+
252249
local command = bin_path .. " --cpus " .. nrCores .. " --tolerance " .. tolerance .. " " .. knownPath .. " " .. path .. " > " .. OUTPUT
253250
os.setlocale()
254251
dt.print_log("Face recognition: Running command: " .. command)
@@ -258,61 +255,77 @@ local function face_recognition ()
258255

259256
-- Open output file
260257
local f = io.open(OUTPUT, "rb")
261-
258+
262259
if not f then
263260
dt.print(_("Face recognition failed"))
264261
else
265262
dt.print(_("Face recognition finished"))
266263
f:close ()
267264
end
268-
265+
269266
-- Read output
270267
dt.print(_("processing results..."))
271268
local result = {}
272-
for line in io.lines(OUTPUT) do
273-
if not string.match(line, "^WARNING:") then
269+
local tags_list = {}
270+
local tag_object = {}
271+
for line in io.lines(OUTPUT) do
272+
if not string.match(line, "^WARNING:") and line ~= "" and line ~= nil then
274273
local file, tag = string.match (line, "(.*),(.*)$")
275274
tag = string.gsub (tag, "%d*$", "")
276275
dt.print_log ("File:"..file .." Tag:".. tag)
277-
if result[file] ~= nil then
278-
table.insert (result[file], tag)
276+
tag_object = {}
277+
if result[file] == nil then
278+
tag_object[tag] = true
279+
result[file] = tag_object
279280
else
280-
result[file] = {tag}
281+
tag_object = result[file]
282+
tag_object[tag] = true
283+
result[file] = tag_object
281284
end
282285
end
283286
end
284-
287+
285288
-- Attach tags
289+
local result_index = 0
286290
for file,tags in pairs(result) do
291+
result_index = result_index +1
287292
-- Find image in table
288-
for img,file2 in pairs(image_table) do
289-
if file == file2 then
290-
for _,t in ipairs (tags) do
291-
-- Check if image is ignored
292-
if ignoreByTag (img, ignoreTags) then
293-
dt.print_log("Face recognition: Ignoring image with ID " .. img.id)
294-
else
295-
-- Check of unrecognized unknown_person
296-
if t == "unknown_person" then
297-
t = unknownTag
298-
end
299-
-- Check of unrecognized no_persons_found
300-
if t == "no_persons_found" then
301-
t = nonpersonsfoundTag
302-
end
303-
if t ~= "" and t ~= nil then
304-
dt.print_log ("ImgId:" .. img.id .. " Tag:".. t)
305-
-- Create tag if it does not exists
306-
local tag = dt.tags.create (t)
307-
img:attach_tag (tag)
293+
img = image_table[file]
294+
if img == nil then
295+
dt.print_log("Face recognition: Ignoring face recognition entry: " .. file)
296+
else
297+
for t,_ in pairs (tags) do
298+
-- Check if image is ignored
299+
if ignoreByTag (img, ignoreTags) then
300+
dt.print_log("Face recognition: Ignoring image with ID " .. img.id)
301+
else
302+
-- Check of unrecognized unknown_person
303+
if t == "unknown_person" then
304+
t = unknownTag
305+
end
306+
-- Check of unrecognized no_persons_found
307+
if t == "no_persons_found" then
308+
t = nonpersonsfoundTag
309+
end
310+
if t ~= "" and t ~= nil then
311+
dt.print_log ("ImgId:" .. img.id .. " Tag:".. t)
312+
-- Create tag if it does not exist
313+
if tags_list[t] == nil then
314+
tag = dt.tags.create (t)
315+
tags_list[t] = tag
316+
else
317+
tag = tags_list[t]
308318
end
319+
img:attach_tag (tag)
309320
end
310321
end
311322
end
312323
end
313324
end
314-
dt.print(_("face recognition complete"))
315325
cleanup(img_list)
326+
dt.print_log("img_list cleaned-up")
327+
dt.print_log("face recognition complete")
328+
dt.print(_("face recognition complete"))
316329
else
317330
dt.print(_("image export failed"))
318331
return
@@ -321,8 +334,6 @@ local function face_recognition ()
321334
dt.print(_("no images selected"))
322335
return
323336
end
324-
325-
326337
end
327338

328339
-- build the interface
@@ -376,7 +387,7 @@ fc.known_image_path = dt.new_widget("file_chooser_button"){
376387
is_directory = true,
377388
changed_callback = function(this)
378389
dt.preferences.write(MODULE, "known_image_path", "directory", this.value)
379-
end
390+
end
380391
}
381392

382393
fc.export_format = dt.new_widget("combobox"){
@@ -403,9 +414,9 @@ fc.height = dt.new_widget("entry"){
403414

404415
fc.execute = dt.new_widget("button"){
405416
label = "detect faces",
406-
clicked_callback = function(this)
417+
clicked_callback = function(this)
407418
face_recognition()
408-
end
419+
end
409420
}
410421

411422
local widgets = {
@@ -422,14 +433,14 @@ local widgets = {
422433
if dt.configuration.running_os == "windows" or dt.configuration.running_os == "macos" then
423434
table.insert(widgets, df.executable_path_widget({"face_recognition"}))
424435
end
425-
table.insert(widgets, dt.new_widget("section_label"){ label = _("processing options")})
426-
table.insert(widgets, fc.tolerance)
427-
table.insert(widgets, fc.num_cores)
428-
table.insert(widgets, fc.export_format)
429-
table.insert(widgets, dt.new_widget("box"){
430-
orientation = "horizontal",
431-
dt.new_widget("label"){ label = _("width ")},
432-
fc.width,
436+
table.insert(widgets, dt.new_widget("section_label"){ label = _("processing options")})
437+
table.insert(widgets, fc.tolerance)
438+
table.insert(widgets, fc.num_cores)
439+
table.insert(widgets, fc.export_format)
440+
table.insert(widgets, dt.new_widget("box"){
441+
orientation = "horizontal",
442+
dt.new_widget("label"){ label = _("width ")},
443+
fc.width,
433444
})
434445
table.insert(widgets, dt.new_widget("box"){
435446
orientation = "horizontal",
@@ -439,18 +450,13 @@ table.insert(widgets, dt.new_widget("box"){
439450
table.insert(widgets, fc.execute)
440451

441452
fc.widget = dt.new_widget("box"){
442-
orientation = vertical,
443-
reset_callback = function(this)
453+
orientation = vertical,
454+
reset_callback = function(this)
444455
reset_preferences()
445-
end,
446-
table.unpack(widgets),
456+
end,
457+
table.unpack(widgets),
447458
}
448459

449-
--fc.tolerance.value = dt.preferences.read(MODULE, "tolerance", "float")
450-
451-
-- Register
452-
--dt.register_storage("module_face_recognition", _("Face recognition"), show_status, face_recognition)
453-
454460
dt.register_lib(
455461
"face_recognition", -- Module name
456462
_("face recognition"), -- Visible name

0 commit comments

Comments
 (0)