Форсировать среднее на изображении


20

Напишите программу , которая принимает в стандартных полноцветный изображениях и одного 24-битного RGB цвета (три числа от 0 до 255). Измените входное изображение (или выведите новое изображение с такими же размерами), чтобы его средний цвет был точно единственным цветом, который был введен. Вы можете изменить пиксели во входном изображении любым удобным для вас способом, но цель состоит в том, чтобы сделать цветовые изменения настолько визуально незаметными, насколько это возможно .

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

Этот скрипт Python 2PIL ) может вычислить средний цвет большинства форматов файлов изображений:

from PIL import Image
print 'Enter image file'
im = Image.open(raw_input()).convert('RGB')
pixels = im.load()
avg = [0, 0, 0]
for x in range(im.size[0]):
    for y in range(im.size[1]):
        for i in range(3):
            avg[i] += pixels[x, y][i]
print 'The average color is', tuple(c // (im.size[0] * im.size[1]) for c in avg)

(Есть аналогичные средний цвет программ здесь , но они не обязательно делать точно такой же расчет) .

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

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

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

Тестовые изображения

Некоторые изображения и их стандартные цвета по умолчанию для игры. Нажмите для полного размера.

А. среднее (127, 127, 127)

От fejesjoco «s Изображения со всеми цветами ответ . Нашел оригинал в своем блоге .

Б. средний (62, 71, 73)

Yokohama . Предоставлено Geobits .

C. средний (115, 112, 111)

Токио . Предоставлено Geobits .

D. средний (154, 151, 154)

Водопад Эшера . Оригинал .

Е. среднее (105, 103, 102)

Гора Шаста . Предоставлено мной.

F. среднее (75, 91, 110)

Звездная ночь

Примечания

  • Точные форматы ввода и вывода и типы файлов изображений, используемые вашей программой, не имеют большого значения. Просто убедитесь, что понятно, как использовать вашу программу.
  • Вероятно, это хорошая идея (но технически не является обязательным требованием), что если изображение уже имеет целевой средний цвет, оно должно выводиться как есть.
  • Пожалуйста, опубликуйте тестовые изображения со средним входным цветом (150, 100, 100) или (75, 91, 110), чтобы избиратели могли видеть одни и те же входные данные в разных решениях. (Публиковать больше примеров, чем это хорошо, даже рекомендуется.)

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

1
@ vihan1086 Если я правильно понял, средний цвет предоставляется как 24-битный цвет RGB, не найденный из входного изображения.
Трихоплакс

3
Может быть интересно использовать интерпретацию @ vihan1086 и использовать примеры изображений в качестве источника входных цветов, чтобы одно изображение отображалось в среднем цвете другого. Таким образом, разные ответы можно сравнивать справедливо.
Трихоплакс

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

@RetoKoradi Надеюсь, что избиратели будут достаточно умны, чтобы принимать во внимание такие вещи, хотя я добавил примечание о том, какие средние цвета по умолчанию использовать.
Увлечения Кэлвина

Ответы:


11

Python 2 + PIL, простое масштабирование цвета

from PIL import Image
import math

INFILE = "street.jpg"
OUTFILE = "output.png"
AVERAGE = (150, 100, 100)

im = Image.open(INFILE)
im = im.convert("RGB")
width, height = prev_size = im.size
pixels = {(x, y): list(im.getpixel((x, y)))
          for x in range(width) for y in range(height)}

def get_avg():
    total_rgb = [0, 0, 0]

    for x in range(width):
        for y in range(height):
            for i in range(3):
                total_rgb[i] += int(pixels[x, y][i])

    return [float(x)/(width*height) for x in total_rgb]

curr_avg = get_avg()

while tuple(int(x) for x in curr_avg) != AVERAGE:
    print curr_avg   
    non_capped = [0, 0, 0]
    total_rgb = [0, 0, 0]

    for x in range(width):
        for y in range(height):
            for i in range(3):
                if curr_avg[i] < AVERAGE[i] and pixels[x, y][i] < 255:
                    non_capped[i] += 1
                    total_rgb[i] += int(pixels[x, y][i])

                elif curr_avg[i] > AVERAGE[i] and pixels[x, y][i] > 0:
                    non_capped[i] += 1
                    total_rgb[i] += int(pixels[x, y][i])

    ratios = [1 if z == 0 else
              x/(y/float(z))
              for x,y,z in zip(AVERAGE, total_rgb, non_capped)]

    for x in range(width):
        for y in range(height):
            col = []

            for i in range(3):
                new_col = (pixels[x, y][i] + 0.01) * ratios[i]
                col.append(min(255, max(0, new_col)))

            pixels[x, y] = tuple(col)

    curr_avg = get_avg()

print curr_avg

for pixel in pixels:
    im.putpixel(pixel, tuple(int(x) for x in pixels[pixel]))

im.save(OUTFILE)

Вот наивный подход, который должен послужить хорошей базой. На каждой итерации мы сравниваем наше текущее среднее с желаемым средним и масштабируем RGB каждого пикселя в соответствии с соответствующим соотношением. Мы должны быть немного осторожны по двум причинам:

  • Масштабирование 0 все еще приводит к 0, поэтому, прежде чем масштабировать, мы добавим что-то маленькое (здесь 0.01)

  • Значения RGB находятся в диапазоне от 0 до 255, поэтому мы должны соответствующим образом изменить соотношение, чтобы компенсировать тот факт, что масштабирование ограниченных пикселей ничего не делает.

Изображения сохраняются в формате PNG, потому что при сохранении в формате JPG средние значения цвета портятся.

Образец вывода

(40, 40, 40)

введите описание изображения здесь введите описание изображения здесь введите описание изображения здесь введите описание изображения здесь введите описание изображения здесь введите описание изображения здесь

(150, 100, 100)

введите описание изображения здесь введите описание изображения здесь введите описание изображения здесь введите описание изображения здесь введите описание изображения здесь введите описание изображения здесь

(75, 91, 110), палитра Звездная ночь

введите описание изображения здесь введите описание изображения здесь введите описание изображения здесь введите описание изображения здесь введите описание изображения здесь введите описание изображения здесь


2
Вы определенно хотите использовать для этого формат изображения со сжатием без потерь. Так что JPEG не очень хороший вариант.
Рето Коради

Вы всегда можете рассчитывать на Sp для решения проблем с изображениями.
Алекс А.

6

C ++, гамма-коррекция

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

Шаги высокого уровня:

  1. Прочитайте изображение и извлеките гистограмму для каждого цветового компонента.
  2. Выполните двоичный поиск значения гаммы для каждого компонента. Бинарный поиск выполняется по значениям гаммы, пока полученная гистограмма не получит желаемое среднее значение.
  3. Прочитайте изображение еще раз и примените гамма-коррекцию.

Все изображения ввода / вывода используют файлы PPM в ASCII. Изображения были конвертированы из / в PNG с использованием GIMP. Код был запущен на Mac, преобразование изображений было сделано в Windows.

Код:

#include <cmath>
#include <string>
#include <vector>
#include <sstream>
#include <fstream>
#include <iostream>

static inline int mapVal(int val, float gamma)
{
    float relVal = (val + 1.0f) / 257.0f;
    float newRelVal = powf(relVal, gamma);

    int newVal = static_cast<int>(newRelVal * 257.0f - 0.5f);
    if (newVal < 0)
    {
        newVal = 0;
    }
    else if (newVal > 255)
    {
        newVal = 255;
    }

    return newVal;
}

struct Histogram
{
    Histogram();

    bool read(const std::string fileName);
    int getAvg(int colIdx) const;
    void adjust(const Histogram& origHist, int colIdx, float gamma);

    int pixCount;
    std::vector<int> freqA[3];
};

Histogram::Histogram()
  : pixCount(0)
{
    for (int iCol = 0; iCol < 3; ++iCol)
    {
        freqA[iCol].resize(256, 0);
    }
}

bool Histogram::read(const std::string fileName)
{
    for (int iCol = 0; iCol < 3; ++iCol)
    {
        freqA[iCol].assign(256, 0);
    }

    std::ifstream inStrm(fileName);

    std::string format;
    inStrm >> format;
    if (format != "P3")
    {
        std::cerr << "invalid PPM header" << std::endl;
        return false;
    }

    int w = 0, h = 0;
    inStrm >> w >> h;
    if (w <= 0 || h <= 0)
    {
        std::cerr << "invalid size" << std::endl;
        return false;
    }

    int maxVal = 0;
    inStrm >> maxVal;
    if (maxVal != 255)
    {
        std::cerr << "invalid max value (255 expected)" << std::endl;
        return false;
    }

    pixCount = w * h;

    int sumR = 0, sumG = 0, sumB = 0;
    for (int iPix = 0; iPix < pixCount; ++iPix)
    {
        int r = 0, g = 0, b = 0;
        inStrm >> r >> g >> b;
        ++freqA[0][r];
        ++freqA[1][g];
        ++freqA[2][b];
    }

    return true;
}

int Histogram::getAvg(int colIdx) const
{
    int avg = 0;
    for (int val = 0; val < 256; ++val)
    {
        avg += freqA[colIdx][val] * val;
    }

    return avg / pixCount;
}

void Histogram::adjust(const Histogram& origHist, int colIdx, float gamma)
{
    freqA[colIdx].assign(256, 0);

    for (int val = 0; val < 256; ++val)
    {
        int newVal = mapVal(val, gamma);
        freqA[colIdx][newVal] += origHist.freqA[colIdx][val];
    }
}

void mapImage(const std::string fileName, float gammaA[])
{
    std::ifstream inStrm(fileName);

    std::string format;
    inStrm >> format;

    int w = 0, h = 0;
    inStrm >> w >> h;

    int maxVal = 0;
    inStrm >> maxVal;

    std::cout << "P3" << std::endl;
    std::cout << w << " " << h << std::endl;
    std::cout << "255" << std::endl;

    int nPix = w * h;

    for (int iPix = 0; iPix < nPix; ++iPix)
    {
        int inRgb[3] = {0};
        inStrm >> inRgb[0] >> inRgb[1] >> inRgb[2];

        int outRgb[3] = {0};
        for (int iCol = 0; iCol < 3; ++iCol)
        {
            outRgb[iCol] = mapVal(inRgb[iCol], gammaA[iCol]);
        }

        std::cout << outRgb[0] << " " << outRgb[1] << " "
                  << outRgb[2] << std::endl;
    }
}

int main(int argc, char* argv[])
{
    if (argc < 5)
    {
        std::cerr << "usage: " << argv[0]
                  << " ppmFileName targetR targetG targetB"
                  << std::endl;
        return 1;
    }

    std::string inFileName = argv[1];

    int targAvg[3] = {0};
    std::istringstream strmR(argv[2]);
    strmR >> targAvg[0];
    std::istringstream strmG(argv[3]);
    strmG >> targAvg[1];
    std::istringstream strmB(argv[4]);
    strmB >> targAvg[2];

    Histogram origHist;
    if (!origHist.read(inFileName))
    {
        return 1;
    }

    Histogram newHist(origHist);
    float gammaA[3] = {0.0f};

    for (int iCol = 0; iCol < 3; ++iCol)
    {
        float minGamma = 0.0f;
        float maxGamma = 1.0f;
        for (;;)
        {
            newHist.adjust(origHist, iCol, maxGamma);
            int avg = newHist.getAvg(iCol);
            if (avg <= targAvg[iCol])
            {
                break;
            }
            maxGamma *= 2.0f;
        }

        for (;;)
        {
            float midGamma = 0.5f * (minGamma + maxGamma);

            newHist.adjust(origHist, iCol, midGamma);
            int avg = newHist.getAvg(iCol);
            if (avg < targAvg[iCol])
            {
                maxGamma = midGamma;
            }
            else if (avg > targAvg[iCol])
            {
                minGamma = midGamma;
            }
            else
            {
                gammaA[iCol] = midGamma;
                break;
            }
        }
    }

    mapImage(inFileName, gammaA);

    return 0;
}

Сам код довольно прост. Одна тонкая, но важная деталь заключается в том, что, хотя значения цвета находятся в диапазоне [0, 255], я сопоставляю их с гамма-кривой, как если бы диапазон был [-1, 256]. Это позволяет принудительно усреднить значение до 0 или 255. В противном случае 0 всегда будет оставаться 0, а 255 всегда будет оставаться 255, что никогда не может составлять в среднем 0/255.

Использовать:

  1. Сохраните код в файле с расширением .cpp, например force.cpp.
  2. Компилировать с c++ -o force -O2 force.cpp.
  3. Беги с ./force input.ppm targetR targetG target >output.ppm.

Пример вывода на 40, 40, 40

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

Af1 BF1 Cf1 df1 eF1 Ff1

Пример вывода на 150, 100, 100:

Af2 Bf2 Cf2 df2 Ef2 Ff2

Пример вывода на 75, 91, 110:

М3 Bf3 Cf3 DF3 EF3 Ff3


Я должен был уменьшить другие изображения, чтобы соответствовать пределу - может быть, попробовать это?
Sp3000

@ Sp3000 Хорошо, теперь все изображения включены. Также с эскизами сейчас. Я закончил тем, что использовал версию JPEG для больших. На самом деле, один из них был ниже предела размера, но похоже, что он был автоматически преобразован в JPEG. Первый и последний примеры - все еще PNG.
Рето Коради

2

Python 2 + PIL

from PIL import Image
import random
import math

SOURCE = 'input.png'
OUTPUT = 'output.png'
AVERAGE = [150, 100, 100]

im = Image.open(SOURCE).convert('RGB')
pixels = im.load()
w = im.size[0]
h = im.size[1]
npixels = w * h

maxdiff = 0.1

# for consistent results...
random.seed(42)
order = range(npixels)
random.shuffle(order)

def calc_sum(pixels, w, h):
    sums = [0, 0, 0]
    for x in range(w):
        for y in range(h):
            for i in range(3):
                sums[i] += pixels[x, y][i]
    return sums

def get_coordinates(index, w):
    return tuple([index % w, index // w])

desired_sums = [AVERAGE[0] * npixels, AVERAGE[1] * npixels, AVERAGE[2] * npixels]

sums = calc_sum(pixels, w, h)
for i in range(3):
    while sums[i] != desired_sums[i]:
        for j in range(npixels):
            if sums[i] == desired_sums[i]:
                break
            elif sums[i] < desired_sums[i]:
                coord = get_coordinates(order[j], w)
                pixel = list(pixels[coord])
                delta = int(maxdiff * (255 - pixel[i]))
                if delta == 0 and pixel[i] != 255:
                    delta = 1
                delta = min(delta, desired_sums[i] - sums[i])

                sums[i] += delta
                pixel[i] += delta
                pixels[coord] = tuple(pixel)
            else:
                coord = get_coordinates(order[j], w)
                pixel = list(pixels[coord])
                delta = int(maxdiff * pixel[i])
                if delta == 0 and pixel[i] != 0:
                    delta = 1
                delta = min(delta, sums[i] - desired_sums[i])

                sums[i] -= delta
                pixel[i] -= delta
                pixels[coord] = tuple(pixel)

# output image
for x in range(w):
    for y in range(h):
        im.putpixel(tuple([x, y]), pixels[tuple([x, y])])

im.save(OUTPUT)

Это повторяет каждый пиксель в случайном порядке и уменьшает расстояние между каждым компонентом цвета пикселя и 255или 0(в зависимости от того, является ли текущее среднее значение меньшим или большим, чем желаемое среднее значение). Расстояние уменьшается на фиксированный мультипликативный коэффициент. Это повторяется до тех пор, пока не будет получено желаемое среднее значение. Сокращение всегда, по крайней мере 1, если цвет не 255(или 0), чтобы гарантировать, что обработка не останавливается, когда пиксель близок к белому или черному.

Образец вывода

(40, 40, 40)

введите описание изображения здесь введите описание изображения здесь введите описание изображения здесь введите описание изображения здесь введите описание изображения здесь введите описание изображения здесь

(150, 100, 100)

введите описание изображения здесь введите описание изображения здесь введите описание изображения здесь введите описание изображения здесь введите описание изображения здесь введите описание изображения здесь

(75, 91, 110)

введите описание изображения здесь введите описание изображения здесь введите описание изображения здесь введите описание изображения здесь введите описание изображения здесь введите описание изображения здесь


1

Джава

Подход, основанный на ГСЧ. Немного медленно для больших входных изображений.

import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.*;

import javax.imageio.ImageIO;


public class Averager {
    static Random r;
    static long sigmaR=0,sigmaG=0,sigmaB=0;
    static int w,h;
    static int rbar,gbar,bbar;
    static BufferedImage i;
    private static File file;
    static void upRed(){
        int x=r.nextInt(w);
        int y=r.nextInt(h);
        Color c=new Color(i.getRGB(x, y));
        if(c.getRed()==255)return;
        sigmaR++;
        c=new Color(c.getRed()+1,c.getGreen(),c.getBlue());
        i.setRGB(x, y,c.getRGB());
    }
    static void downRed(){
        int x=r.nextInt(w);
        int y=r.nextInt(h);
        Color c=new Color(i.getRGB(x, y));
        if(c.getRed()==0)return;
        sigmaR--;
        c=new Color(c.getRed()-1,c.getGreen(),c.getBlue());
        i.setRGB(x, y,c.getRGB());
    }
    static void upGreen(){
        int x=r.nextInt(w);
        int y=r.nextInt(h);
        Color c=new Color(i.getRGB(x, y));
        if(c.getGreen()==255)return;
        sigmaG++;
        c=new Color(c.getRed(),c.getGreen()+1,c.getBlue());
        i.setRGB(x, y,c.getRGB());
    }
    static void downGreen(){
        int x=r.nextInt(w);
        int y=r.nextInt(h);
        Color c=new Color(i.getRGB(x, y));
        if(c.getGreen()==0)return;
        sigmaG--;
        c=new Color(c.getRed(),c.getGreen()-1,c.getBlue());
        i.setRGB(x,y,c.getRGB());
    }
    static void upBlue(){
        int x=r.nextInt(w);
        int y=r.nextInt(h);
        Color c=new Color(i.getRGB(x, y));
        if(c.getBlue()==255)return;
        sigmaB++;
        c=new Color(c.getRed(),c.getGreen(),c.getBlue()+1);
        i.setRGB(x, y,c.getRGB());
    }
    static void downBlue(){
        int x=r.nextInt(w);
        int y=r.nextInt(h);
        Color c=new Color(i.getRGB(x, y));
        if(c.getBlue()==0)return;
        sigmaB--;
        c=new Color(c.getRed(),c.getGreen(),c.getBlue()-1);
        i.setRGB(x,y,c.getRGB());
    }
    public static void main(String[]a) throws Exception{
        Scanner in=new Scanner(System.in);
        i=ImageIO.read(file=new File(in.nextLine()));
        rbar=in.nextInt();
        gbar=in.nextInt();
        bbar=in.nextInt();
        w=i.getWidth();
        h=i.getHeight();
        final int npix=w*h;
        r=new Random(npix*(long)i.hashCode());
        for(int x=0;x<w;x++){
            for(int y=0;y<h;y++){
                Color c=new Color(i.getRGB(x, y));
                sigmaR+=c.getRed();
                sigmaG+=c.getGreen();
                sigmaB+=c.getBlue();
            }
        }
        while(sigmaR/npix<rbar){
            upRed();
        }
        while(sigmaR/npix>rbar){
            downRed();
        }
        while(sigmaG/npix<gbar){
            upGreen();
        }
        while(sigmaG/npix>gbar){
            downGreen();
        }
        while(sigmaB/npix<bbar){
            upBlue();
        }
        while(sigmaB/npix>bbar){
            downBlue();
        }
        String k=file.getName().split("\\.")[0];
        ImageIO.write(i,"png",new File(k="out_"+k+".png"));
    }
}

тесты:

(40,40,40)

введите описание изображения здесьвведите описание изображения здесьвведите описание изображения здесьвведите описание изображения здесьвведите описание изображения здесьвведите описание изображения здесь

(150100100)

введите описание изображения здесьвведите описание изображения здесьвведите описание изображения здесьвведите описание изображения здесьвведите описание изображения здесьвведите описание изображения здесь

(75,91,110)

введите описание изображения здесьвведите описание изображения здесьвведите описание изображения здесьвведите описание изображения здесьвведите описание изображения здесьвведите описание изображения здесь

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.