Задайте середнє значення на зображення


20

Напишіть програму, яка має стандартне зображення truecolor та єдиний 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)

Йокогама . Надано Geobits .

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

Токіо . Надано Geobits .

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

Водоспад Ешера . Оригінал .

E. середній (105, 103, 102)

Гора Шаста . Наданий мною.

Середній F. (75, 91, 110)

Зоряна ніч

Примітки

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

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

1
@ vihan1086 Якщо я правильно зрозумів, середній колір надається як 24-бітний кольоровий RGB-вхід, не знайдений із вхідного зображення.
трихоплакс

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

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

@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:

Af3 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

Java

Підхід на основі РНГ. Трохи повільно для великих вхідних зображень.

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)

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

(150 10000)

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

(75,91,110)

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.