Pull to refresh

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

Reading time 7 min
Views 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.
Tags:
Hubs:
+14
Comments 6
Comments Comments 6

Articles