Как стать автором
Обновить

Опять XMP тэги лиц. Все плохо, но это поправимо

Время на прочтение7 мин
Количество просмотров7.8K
На хабре уже несколько раз поднимался вопрос про хранение метатегов распознаных лиц для фото-архива, к сожалению из приведенных рецептов ни один не оказался достаточно рабочим, да и гугление не помогло, поэтому пришлось писать свой велосипедик.

Оригинальные статьи: раз и два.

Все там написано хорошо и правильно, но счастья все равно нет.

Картинка для привлечения внимания:


Задача простая — отметить людей на фотографии и иметь возможность пользоваться этим в максимальном количестве мест любым удобным для меня способом.

Лица отмечать буду в пикасе, потому как привязка к гугловским контактам, кроссплатформенность, авто определение, хранение тегов внутри файла (с нюансами). А вот смотреть и использовать эти теги хочу везде в 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.
Теги:
Хабы:
+14
Комментарии6

Публикации

Истории

Ближайшие события

Weekend Offer в AliExpress
Дата20 – 21 апреля
Время10:00 – 20:00
Место
Онлайн
Конференция «Я.Железо»
Дата18 мая
Время14:00 – 23:59
Место
МоскваОнлайн