На хабре уже несколько раз поднимался вопрос про хранение метатегов распознаных лиц для фото-архива, к сожалению из приведенных рецептов ни один не оказался достаточно рабочим, да и гугление не помогло, поэтому пришлось писать свой велосипедик.
Оригинальные статьи: раз и два.
Все там написано хорошо и правильно, но счастья все равно нет.
Картинка для привлечения внимания:
Задача простая — отметить людей на фотографии и иметь возможность пользоваться этим в максимальном количестве мест любым удобным для меня способом.
Лица отмечать буду в пикасе, потому как привязка к гугловским контактам, кроссплатформенность, авто определение, хранение тегов внутри файла (с нюансами). А вот смотреть и использовать эти теги хочу везде в gallery3 потому что она это умеет, в lightroom потому что именно им пользуюсь как каталогизатором, в microsoft explorer потому как он у меня есть и в Microsoft Live Photo Gallery, просто потому что это второй популярный формат и почему бы и его тоже не использовать.
Кто тоже хочет заморочиться — добро пожаловать под кат.
Проблема номер 1 где пикаса хранит информацию о лицах?
Вариантов три:
- Либо это связка из двух файлов: contacts.xml в профиле пользователя и picasa.ini в папке с фотографиями. Этот вариант верен если вы тегировали лица в пикасе версии меньше чем 3.9 и в последней пикасе, о с выключенной галкой «Store name tags in photo».
- Второй вариант хранение лиц в XMP-mwg-rs если вы использовали только последнюю пикасу и включили галку «Store name tags in photo» до того как начали отмечать лица, надо помнить, что по умолчанию она включена.
- И наконец третий вариант, самый распространенный: лица хранятся и там и там и что с этим делать вообще непонятно.
По понятным причинам второй вариант самый оптимальный, но как оказалось не все так просто, пикасе нельзя просто сказать: «Запиши все теги в файлы» гугл пожадничал поставить такую кнопку. Вариантов решить эту проблему есть несколько как чисто пикасовых так и используя внешние утилиты.
picface
почти работает, недавно обновлялась, но у меня многократно вылетала не доделав начатое до конца, а поскольку архив у меня примерно 60К фотографий, все это происходит долго и муторно.
самый популярный avpicfacexmptagger
Умеет делать почти все, что надо, но достаточно однобоко, давно не обновлялся, придумали свою собственную схему для xmp, можно подумать существующих мало. Но в принципе для приведения архива к состоянию два использовать можно.
Я использовал рекомендацию от самого гугла, вам нужна только пикаса версии 3.9 с включенной галкой «Store name tags in photo». Для гарантированной записи всех лиц в xmp надо пройтись по всем людям и переименовать во что-то, а потом переименовать обратно. Быстро, просто, работает. На вкладке people два раза кликаете по первому имени и переименовываете его в 'x' потом два раза кликаете по 'x' и переименовываете его обратно. Все у меня оттегировано полторы сотни людей и это заняло всего пару минут. Пикасу после этого лучше сразу не закрывать, а дать ей какое-то время записать данные на диск, потому как прогресса никакого она не показывает.
Следующим шагом мы хотим извлечь имена записаные пикасой в xmp-mwg-rs в теге RegionName и записать его в теги PersonInImage и RegionPersonDisplayName после этого мы сможем использовать эти теги при поиске во всех каталогизаторах и даже microsoft explorer будет нам показывать в информации имена людей на фотографии. Сделать это проще всего при помощи exiftool который можно скачать здесь.
exiftool -RegionName>PersonInImage photo.jpg
exiftool -RegionName>RegionPersonDisplayName photo.jpg
после этого мы видим информацию о людях во множестве сторонних програм
Так же можно конвертировать информацию о положении лиц на кадре из стандарта пикасы в стандарт от микрософта, отличаются они не только именами, но даже методом как считаются квадраты, одни задают длинну и высоту от верхнего левого угла квадрата, а другие от центра. Для конвертации нам нужен конфиг для exiftool.
ExifTool_config_convert_regions
%Image::ExifTool::UserDefined = (
'Image::ExifTool::Composite' => {
MyRegion => {
Require => {
0 => 'RegionInfoMP',
1 => 'ImageWidth',
2 => 'ImageHeight',
},
ValueConv => q{
my ($rgn, @newRgns);
foreach $rgn (@{$val[0]{Regions}}) {
my @rect = split /s*,s*/, $$rgn{Rectangle};
my %newRgn = (
Area => {
X => $rect[0] + $rect[2]/2,
Y => $rect[1] + $rect[3]/2,
W => $rect[2],
H => $rect[3],
Unit => 'normalized',
},
Name => $$rgn{PersonDisplayName},
Type => 'Face',
);
push @newRgns, %newRgn;
}
return {
AppliedToDimensions => { W => $val[1], H => $val[2], Unit => 'pixel' },
RegionList => @newRgns,
};
},
},
MyRegionMP => {
Require => 'RegionInfo',
ValueConv => q{
my ($rgn, @newRgns);
foreach $rgn (@{$val[0]{RegionList}}) {
my @rect = @{$$rgn{Area}}{'X','Y','W','H'};
$rect[0] -= $rect[2]/2;
$rect[1] -= $rect[3]/2;
push @newRgns, {
PersonDisplayName => $$rgn{Name},
Rectangle => join(', ', @rect),
};
}
return { Regions => @newRgns };
},
},
},
);
1; #end
Конфиг найден на просторах интернета и он позволяет конвертировать регионы в обе стороны, но нам достаточно в одну. Для этого выполняем exiftool со следующими параметрами:
exiftool -config ExifTool_config_convert_regions "-regioninfomp<MyRegionMP"' photo.jpg
после этого лицо правильно отображается в Microsoft Live Photo Gallery и другом софте который придерживается той же схемы
Уже практически профит, но еще бы автоматизировать это дело для всех необходимых фотографий, а кроме того прописать имена в ключевые слова: теги Subject и HierarchialSuject да, здесь тоже не обошлось без двойных форматов. Для этих целей я написал плагинчик для лайтрума, это дает возможность запустить его только на нужных фотографиях, и добавлять ключевые слова, не опасаясь их задвоить или стереть уже существующие, ну и просто потому что я пользуюсь именно лайтрумом как каталогизатором всего архива.
За код просьба не бить, а лучше подсказать как его улучшить, это мой первый плагин для LR и вообще первый раз когда я увидел lua.
В плагине всего два файла
Info.lua
return {
LrSdkVersion = 3.0,
LrSdkMinimumVersion = 1.3, -- minimum SDK version required by this plug-in
LrToolkitIdentifier = 'com.adobe.lightroom.sdk.helloworld',
LrPluginName = LOC "$$$/PicasaFaceToTag/PluginName=Picasa Faces to Tags",
-- Add the menu item to the Library menu.
LrLibraryMenuItems = {
{ title = "Write Picasa Faces to Tags", file = "PersonInImage.lua"},
},
VERSION = { major=4, minor=1, revision=0, build=831116, },
}
PersonInImage.lua
--[[----------------------------------------------------------------------------
------------------------------------------------------------------------------]]
-- Access the Lightroom SDK namespaces.
local LrTasks = import 'LrTasks'
local LrProgressScope = import 'LrProgressScope'
local LrApplication = import 'LrApplication'
local catalog = LrApplication.activeCatalog()
local photos = catalog:getTargetPhotos()
local LrPathUtils = import 'LrPathUtils'
local logger = import 'LrLogger'("lr")
logger:enable('print')
local function faceToTag()
--[[Convert faces from picasa xmp tag to microsoft xmp ]]
exeFile = LrPathUtils.child( _PLUGIN.path, "exiftool.exe" )
cfgFile = LrPathUtils.child( _PLUGIN.path, "ExifTool_config_convert_regions" )
redirect = LrPathUtils.getStandardFilePath('temp') .. "exiftool.stdout"
local total = ( # catalog:getTargetPhotos() )
local exifArgs = {"-b -RegionName >" .. redirect,
--'-overwrite_original "-RegionName>PersonInImage"',
'-overwrite_original "-RegionName>RegionPersonDisplayName"',
'-config '..cfgFile..' -overwrite_original "-regioninfomp<MyRegionMP"'}
local progressScope = LrProgressScope{
title = "Write Picasa Faces to Tags",
caption = "Updateting " .. total .. " photos." ,
}
progressScope:setCancelable( true )
local parrent
catalog:withWriteAccessDo("Create parrent keyword", function ()
parrent = catalog:createKeyword("names", {}, false, nil, true)
--logger:debug("parrent keyword created: " .. tostring(parrent))
end)
for completed, photo in ipairs(photos) do
progressScope:setPortionComplete(completed, total)
progressScope:setCaption("Updated " .. tostring(completed) .. " of " .. tostring(total) .. " photos")
if progressScope:isCanceled() then progressScope:done() break end
local path = photo:getRawMetadata('path')
logger:debug(path) -- write filename to debug log
for i,exifArg in ipairs(exifArgs) do
local exeCmd ='"' .. exeFile.." "..exifArg.." "..path .. '"'
local status = LrTasks.execute(exeCmd)
if io.open(redirect):read() == nil then break end --check is there any names in the file
--logger:debug(exeCmd)
if status ~= 0
then logger:debug("Error "..exeCmd)
progressScope:done()
end
end
for name in io.lines(redirect) do
if name ~= nil then -- check is there any pleople on photo
logger:debug(name)
catalog:withWriteAccessDo("Adding name keywords", function ()
local keyword = catalog:createKeyword(name, {}, true, parrent, true)
logger:debug("keyword created: " .. tostring(keyword))
photo:addKeyword(keyword)
--photo:setRawMetadata('personShown', keyword) --doesn't work
logger:debug("keyword added: " .. name)
end)
end
end
end
progressScope:done()
end
LrTasks.startAsyncTask(faceToTag)
Все именные теги хранятся в иерархической структуре внутри тега «names», программы которые не работают с xmp схемой лайтрума будут видеть их просто плоским списком + тег «names». Для работы плагину в папку надо положить exiftool.exe и его конфиг. Все скопом можно скачать с github
Плагин работает, но у него есть недостатки:
- Не удается записать PersonInImage, если его писать exiftool он перезаписывается лайтрумом, а записать его непосредственно через SDK не получается по непонятным причинам. photo:setRawMetadata('personShown', keyword) вылетает с ошибкой. Можно разнести это в две разных кнопки и перечитывать метаданные вручную, но это тоже некрасиво.
- работает медленно примерно 1 секунда на фото, при большем архиве это проблема, возможно если переписать через cookbooks.adobe.com/post_ExifTool___making_it_scream_-19501.html будет быстрее.
- Поскольку плагин пишет одновременно и через exiftool и через lightroom SDK по окончании работы регулярно возникает конфликт метаданных, и их надо сохранять вручную Ctrl+s. Если кто-то знает как заставить лайтрум писать и читать метаданные програмно — отпишитесь. Я пока придумал вариант только с эмуляцией хотеев lrBind но это не красиво.
- Это уже проблема не плагина, всей системы, если протегировать рав, а после этого его экспортировать, информация о лицах теряется, но теги сохраняются, а это уже профит.
ЗЫ. Я смог найти два плагина которые делают _почти_ тоже самое, берут лица из пикасы и пишут их в теги, но все они берут лица только из picasa.ini и не работают с лицами записанными в XMP.
Автор: kefiiir