Как настроить цвета большого количества изображений на основе одного вида? - PullRequest
1 голос
/ 04 октября 2019

У меня есть большое количество фотографий, которые я хочу вывести на один и тот же «уровень» - одни и те же цвета / яркость / контрастность и т.д. с цветами), который я добавил ко всем другим фотографиям.

Это начальное / руководство https://imgur.com/a/Jlozy1e, и вот некоторые из фотографий https://imgur.com/JUsKMt2, https://imgur.com/PvqsleR, https://imgur.com/tcMROU9

На мой взгляд, область с маленькими квадратными цветами (квадраты управления цветом) должна быть одинакового цвета (шестнадцатеричное значение) на всех фотографиях, чтобы они были одинаковымиУровень - так что я могу получить значимые данные из полосы ниже.

Есть ли способ сделать это автоматически или в пакетном режиме в фотошопе или каком-либо другом инструменте?

РЕДАКТИРОВАТЬ: обратите внимание, что могут быть более темные / более светлые области, чем в контрольных квадратах, которые яхотите сохранить их (просто соответственно сделайте светлее / темнее, но не полностью заменяйте их пороговым цветом)

Ответы [ 3 ]

0 голосов
/ 04 октября 2019

Я бы автоматизировал это с помощью ImageMagick , который установлен на большинстве дистрибутивов Linux и доступен для macOS и Windows.

Сначала я запустил бы скрипт, чтобы получить черные и белые точкииз вашего калибровочного изображения. Это обрезает квадрат 50x50 от черно-белых концов калибровочной полосы и вычисляет их средние значения, усредненные по квадрату 50x50. Это выглядит так:

#!/bin/bash

# Check parameters
if [ $# -ne 1 ] ; then
   echo "Usage: calibrate CALIBRATIONIMAGE" >&2 
   exit 1
fi
# Pick up parameter
image=$1
check="check-$image"

# User-adjustable x and y corrdinates of top-left corner of black and white rectangles
blkx0=660
blky0=300
whtx0=40
whty0=300

# Calculate bottom-right corners of rectangles, given top-left
((blkx1=blkx0+50))
((blky1=blky0+50))
((whtx1=whtx0+50))
((whty1=whty0+50))

# Output a check showing where we got black and white points from
convert "$image" -fill none \
    -stroke red  -draw "rectangle $blkx0,$blky0 $blkx1,$blky1" \
    -stroke blue -draw "rectangle $whtx0,$whty0 $whtx1,$whty1" \
    "$check"

# Output black and white points (as rounded percentages)
blkpt=$(convert "$image" -crop 50x50+$blkx0+$blky0 -format "%[fx:round(mean*100)]" info:)
whtpt=$(convert "$image" -crop 50x50+$whtx0+$whty0 -format "%[fx:round(mean*100)]" info:)

echo "[$image]: Black point: $blkpt, white point: $whtpt. Check image: [$check]"

И вы запустите:

./calibrate calibration.png

и получите следующий вывод:

./calibrate calibration.png
[calibration.png]: Black point: 5, white point: 91. Check image: [check-calibration.png]

enter image description here

Итак, теперь мы знаем, что средняя яркость в красном квадрате равна 5, а средняя яркость в синем квадрате равна 91, и мы можем проверить, откуда были извлечены квадраты.

Теперь нам нужно применить это к другим изображениям. Давайте сначала сделаем один. Код для apply:

#!/bin/bash

# Check parameters
if [ $# -ne 3 ] ; then
   echo "Usage: apply blackpoint whitepoint image" >&2
   exit 1
fi

# Pick up parameters
newblkpt=$1
newwhtpt=$2
image=$3
newname="corrected-$image"

# User-adjustable x and y coordinates of top-left corner of black and white rectangles
blkx0=670
blky0=100
whtx0=50
whty0=100

# Calculate bottom-right corners of rectangles, given top-left
((blkx1=blkx0+50))
((blky1=blky0+50))
((whtx1=whtx0+50))
((whty1=whty0+50))

# Output a check showing where we got black and white points from
convert "$image" -fill none \
    -stroke red  -draw "rectangle $blkx0,$blky0 $blkx1,$blky1" \
    -stroke blue -draw "rectangle $whtx0,$whty0 $whtx1,$whty1" \
    check-$image.png

# Get current black and white points
blkpt=$(convert "$image" -crop 50x50+$blkx0+$blky0 -format "%[fx:round(mean*100)]" info:)
whtpt=$(convert "$image" -crop 50x50+$whtx0+$whty0 -format "%[fx:round(mean*100)]" info:)

# The following line actually does the entire calibration!
convert "$image" -level ${blkpt},${whtpt}% +level ${newblkpt},${newwhtpt}% "$newname"
echo "[$image]: Black point: $blkpt, white point: $whtpt => [$newname]: Black point: $newblkpt, white point: $newwhtpt"

Итак, если мы запустим это и применим калибровку, которую мы только что узнали из 5, 91 к im1.png, мы получим:

./apply 5 91 im1.png 
[im1.png]: Black point: 4, white point: 71 => [corrected-im1.png]: Black point: 5, white point: 91 

Это дает нам это исправленное изображение (со значительно поднятым белым цветом):

enter image description here

и это проверочное изображение, показывающее, по каким областям мы калибровали:

enter image description here

Итак, нам просто нужен цикл для создания всех изображений в каталоге:

for f in *.png ; do
    ./apply 5 91 "$f"
done

Это дает нам следующие результаты:

enter image description here

Ключевые слова : ImageMagick, командная строка, командная строка, изображение, обработка изображений, калибровка, калибровка, калибровочная полоса, тест-полоска.

Обратите внимание, что если вы используете ImageMagick v7 или новее, замените команду convert на magick в обоих сценариях.

0 голосов
/ 04 октября 2019

Если вы хотите сделать это с помощью Photoshop, вам необходимо получить усредненное значение для черного калибровочного квадрата в калибровочном изображении, открыв окно гистограммы, а затем нарисовав область над черным квадратом и отметив среднее значение (11,89):

enter image description here

Затем, как и для белого калибровочного квадрата, следует отметить среднее значение 231:

enter image description here

Тогда вам нужно получить те же два значения в вашем некалиброванном изображении. Значение черного составляет 10:

enter image description here

А значение белого составляет 180:

enter image description here

Теперь добавьте Слой корректировки уровней (см. Зеленую область) и введите значения сверху (синяя область):

enter image description here

Итак, я полагаю, вы можете создать ярлык, который добавляет корректирующий слой Levels с двумя запрограммированными значениями калибровочного изображения и применяет его ко всем изображениям. Вам просто нужно вручную добавить два других значения для каждого конкретного изображения.

0 голосов
/ 04 октября 2019

Я не знаю, возможно ли это с помощью какого-либо продвинутого инструмента, но вот мой взгляд на Photoshop. Идея довольно проста - использовать карту градиента для переназначения целевых цветов в исходные значения (следовательно, это не сработает на 32-битных форматах):

  1. выборка исходных цветов из активного документа (исходного документа);
  2. запросить путь для открытия других документов, начать открывать их один за другим;
  3. выбрать образцы целевых цветов и получить их положение для карты градиента
  4. использовать исходные цветаи целевые позиции для создания карты градиента

Вот результат, который я получил: левая строка - это исходные документы с фрагментом исходных квадратов сверху для справки, правая строка - результат-документы сприменяется карта градиента и тот же фрагмент сверху исходного документа (едва видимый):

enter image description here

И вот сценарий, который я сделал.

Обратите внимание, что я использовал ваши файлы png, поэтому, если ваши файлы имеют другой размер, вам может потребоваться настроить координаты для цветовых пробоотборников.

var sampler, sampledColors, sourceCoords, targetCoords;

sourceCoords = [
    [55, 318],
    [190, 318],
    [310, 318],
    [420, 318],
    [560, 318],
    [690, 318],
];

targetCoords = [
    [78, 120],
    [206, 120],
    [328, 120],
    [453, 120],
    [577, 120],
    [709, 120],
]

var Utils = Utils ||
{
    addSample: function(coord)
    {
        return app.activeDocument.colorSamplers.add(coord);
    },
    readSample: function(sample)
    {
        return sample.color;
    },
    getSamplers: function()
    {
        return app.activeDocument.colorSamplers;
    },
    deleteSample: function(sample)
    {
        sample.remove();
    },
    rgb2yuv: function(rgb)
    {
        var r = rgb[0] / 255,
            g = rgb[1] / 255,
            b = rgb[2] / 255;

        var y = (r * 0.299) + (g * 0.587) + (b * 0.114);
        var u = (r * -0.14713) + (g * -0.28886) + (b * 0.436);
        var v = (r * 0.615) + (g * -0.51499) + (b * -0.10001);

        return [y, u, v];
    },
    linear: function(X, A, B, C, D, _cut)
    {
        var _cut = _cut !== undefined ? _cut : false;
        var Y = (X - A) / (B - A) * (D - C) + C
        if (_cut)
        {
            if (Y > D) Y = D;
            if (Y < C) Y = C;
        }
        return Y;
    },
    docToRgb: function()
    {
        var desc16 = new ActionDescriptor();
        desc16.putClass(charIDToTypeID('T   '), charIDToTypeID('RGBM'));
        desc16.putBoolean(charIDToTypeID('Fltt'), false);
        desc16.putBoolean(charIDToTypeID('Rstr'), false);
        executeAction(charIDToTypeID('CnvM'), desc16, DialogModes.NO);
    },
    rectangleSelection: function(coord)
    {
        var delta = 7;
        var descRectangleSelection = new ActionDescriptor();
        var rectSelectionRef = new ActionReference();
        rectSelectionRef.putProperty(charIDToTypeID('Chnl'), charIDToTypeID('fsel'));
        descRectangleSelection.putReference(charIDToTypeID('null'), rectSelectionRef);
        var descCoords = new ActionDescriptor();
        descCoords.putUnitDouble(charIDToTypeID('Top '), charIDToTypeID('#Pxl'), coord[1] - delta);
        descCoords.putUnitDouble(charIDToTypeID('Left'), charIDToTypeID('#Pxl'), coord[0] - delta);
        descCoords.putUnitDouble(charIDToTypeID('Btom'), charIDToTypeID('#Pxl'), coord[1] + delta);
        descCoords.putUnitDouble(charIDToTypeID('Rght'), charIDToTypeID('#Pxl'), coord[0] + delta);
        descRectangleSelection.putObject(charIDToTypeID('T   '), charIDToTypeID('Rctn'), descCoords);
        executeAction(charIDToTypeID('setd'), descRectangleSelection, DialogModes.NO);
    },
    saveTIF: function(data)
    {
        if (!new Folder(data.path).exists) new Folder(data.path).create();
        var desc = new ActionDescriptor();
        var descOptions = new ActionDescriptor();
        descOptions.putEnumerated(charIDToTypeID('BytO'), charIDToTypeID('Pltf'), charIDToTypeID('Mcnt'));
        descOptions.putEnumerated(stringIDToTypeID('layerCompression'), charIDToTypeID('Encd'), stringIDToTypeID('RLE'));
        desc.putObject(charIDToTypeID('As  '), charIDToTypeID('TIFF'), descOptions);
        desc.putPath(charIDToTypeID('In  '), new File(data.path + "/" + data.name + ".tif"));
        executeAction(charIDToTypeID('save'), desc, DialogModes.NO);
    },
};

var getSamplersData = function(coordinates)
{
    var colors = [];
    var color, sampler;

    Utils.docToRgb();

    for (var i = 0; i < coordinates.length; i++)
    {
        Utils.rectangleSelection(coordinates[i]);
        activeDocument.activeLayer.applyAverage();
        activeDocument.selection.deselect();

        sampler = Utils.addSample(coordinates[i]);
        color = Utils.readSample(sampler);
        colors.push(color);
        Utils.deleteSample(sampler);
    }
    return colors;
};

var setSamplerData = function()
{
    var workFolder;

    var controller = function(originalColors)
    {
        var docs, doc, docSampler, sampledColors, gradientColors;

        try
        {
            docs = getDocs();
        }
        catch (e)
        {
            return false;
        }


        for (var i = 0; i < docs.length; i++)
        {
            try
            {
                doc = openDocument(docs[i]);
            }
            catch (e)
            {
                return false;
            }

            sampledColors = getSamplersData(targetCoords);

            gradientColors = createGradientDataFromColors(originalColors, sampledColors);

            createGradient(gradientColors);

            Utils.saveTIF(
            {
                path: workFolder + "/export",
                name: activeDocument.name
            });
        }
    };

    /////////////////////////////////////////////////////////////////////////////////////
    var getDocs = function()
    {
        var docs;

        workFolder = Folder.selectDialog();
        if (workFolder == null) throw 'cancelled';

        docs = workFolder.getFiles('*');

        for (var i = docs.length - 1; i >= 0; i--)
        {

            if (docs[i] instanceof Folder) docs.splice(i, 1);
        }

        if (docs.length == 0) throw 'no files in the folder';

        return docs;
    }; // end of getDocs()

    var openDocument = function(path)
    {
        var doc;
        try
        {
            doc = app.open(new File(path));
            Utils.docToRgb();
            return doc;
        }
        catch (e)
        {
            alert("can't open " + path + "\nAborting");
            throw e;
        }
    };

    var createGradientDataFromColors = function(original, sampled)
    {
        var colors = [];
        var rgbOriginal, rgbSampled, positionSampled;

        for (var i = 0; i < original.length; i++)
        {
            rgbOriginal = getRGB(original[i]);
            rgbSampled = getRGB(sampled[i]);
            positionSampled = Math.round(Utils.rgb2yuv(rgbSampled)[0] * 10000) / 100;

            colors.push(
            {
                color: rgbOriginal,
                pos: positionSampled
            });
        }

        return colors;
    }; // end of createGradientDataFromColors()

    var getRGB = function(color)
    {
        return [color.rgb.red, color.rgb.green, color.rgb.blue];
    }; // end of getRGB()

    var createGradient = function(data)
    {
        var descGradMap = new ActionDescriptor();
        var referenceMap = new ActionReference();
        referenceMap.putClass(charIDToTypeID('AdjL'));
        descGradMap.putReference(charIDToTypeID('null'), referenceMap);
        var desc5 = new ActionDescriptor();
        var desc6 = new ActionDescriptor();
        var desc7 = new ActionDescriptor();

        desc7.putEnumerated(charIDToTypeID('GrdF'), charIDToTypeID('GrdF'), charIDToTypeID('CstS'));
        desc7.putDouble(charIDToTypeID('Intr'), 4096.000000);

        var list1 = new ActionList();
        var el;

        for (var i = 0; i < data.length; i++)
        {
            el = data[i];

            var descTemp = new ActionDescriptor();
            var descColor = new ActionDescriptor();
            descColor.putDouble(charIDToTypeID('Rd  '), el.color[0]);
            descColor.putDouble(charIDToTypeID('Grn '), el.color[1]);
            descColor.putDouble(charIDToTypeID('Bl  '), el.color[2]);
            descTemp.putObject(charIDToTypeID('Clr '), charIDToTypeID('RGBC'), descColor);
            descTemp.putEnumerated(charIDToTypeID('Type'), charIDToTypeID('Clry'), charIDToTypeID('UsrS'));
            descTemp.putInteger(charIDToTypeID('Lctn'), Utils.linear(el.pos, 0, 100, 0, 4096));
            descTemp.putInteger(charIDToTypeID('Mdpn'), 50);
            list1.putObject(charIDToTypeID('Clrt'), descTemp);
        }

        desc7.putList(charIDToTypeID('Clrs'), list1);

        var list2 = new ActionList();
        var desc12 = new ActionDescriptor();
        desc12.putUnitDouble(charIDToTypeID('Opct'), charIDToTypeID('#Prc'), 100.000000);
        desc12.putInteger(charIDToTypeID('Lctn'), 0);
        desc12.putInteger(charIDToTypeID('Mdpn'), 50);
        list2.putObject(charIDToTypeID('TrnS'), desc12);
        var desc13 = new ActionDescriptor();
        desc13.putUnitDouble(charIDToTypeID('Opct'), charIDToTypeID('#Prc'), 100.000000);
        desc13.putInteger(charIDToTypeID('Lctn'), 4096);
        desc13.putInteger(charIDToTypeID('Mdpn'), 50);
        list2.putObject(charIDToTypeID('TrnS'), desc13);
        desc7.putList(charIDToTypeID('Trns'), list2);

        desc6.putObject(charIDToTypeID('Grad'), charIDToTypeID('Grdn'), desc7);
        desc5.putObject(charIDToTypeID('Type'), charIDToTypeID('GdMp'), desc6);

        descGradMap.putObject(charIDToTypeID('Usng'), charIDToTypeID('AdjL'), desc5);
        executeAction(charIDToTypeID('Mk  '), descGradMap, DialogModes.NO);
    };

    return controller;
};

sampledColors = getSamplersData(sourceCoords);

sampler = setSamplerData();
sampler(sampledColors);
...