Forza una media su un'immagine


20

Scrivi un programma che includa un'immagine truecolor standard e un singolo colore RGB a 24 bit (tre numeri da 0 a 255). Modifica l'immagine in ingresso (o in uscita una nuova immagine con le stesse dimensioni) in modo che il suo colore medio sia esattamente il colore singolo che è stato inserito. Puoi modificare i pixel nell'immagine di input in qualsiasi modo ti piaccia, ma l'obiettivo è quello di rendere i cambiamenti di colore visivamente il più impercettibili possibile .

Il colore medio di un'immagine RGB è in realtà un insieme di tre mezzi aritmetici , uno per ciascun canale di colore. Il valore rosso medio è la somma dei valori rossi su tutti i pixel dell'immagine divisa per il numero totale di pixel (l'area dell'immagine), arrotondato per difetto all'intero più vicino. Le medie verde e blu sono calcolate allo stesso modo.

Questo script Python 2 (con PIL ) può calcolare il colore medio della maggior parte dei formati di file immagine:

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)

(Esistono programmi di colore medio simili qui , ma non eseguono necessariamente lo stesso calcolo esatto.)

Il requisito principale per il tuo programma è che per qualsiasi immagine di input, il colore medio dell'output corrispondente deve corrispondere esattamente al colore che è stato immesso, come giudicato dallo snippet di Python o da un codice equivalente. L'immagine di output deve inoltre avere le stesse dimensioni dell'immagine di input.

Quindi potresti tecnicamente inviare un programma che colora semplicemente l'intero input del colore medio specificato (perché la media sarebbe sempre quel colore), ma questo è un concorso di popolarità - vincerà l'invio con il maggior numero di voti e un tale banale l'invio non ti darà molti voti. Nuove idee come sfruttare le stranezze nella visione umana, o ridurre l'immagine e tracciare un bordo colorato attorno ad essa (si spera) otterranno voti.

Si noti che alcune combinazioni di colori e immagini medi richiedono cambiamenti di colore estremamente evidenti. Ad esempio, se il colore medio da abbinare fosse nero (0, 0, 0), qualsiasi immagine di input dovrebbe essere resa completamente nera perché se qualsiasi pixel avesse valori diversi da zero, renderebbe anche il valore medio diverso da zero ( salvo errori di arrotondamento). Tenere presenti tali limiti durante il voto.

Immagini di prova

Alcune immagini e i loro colori medi predefiniti con cui giocare. Clicca per dimensioni intere.

A. media (127, 127, 127)

Da fejesjoco 's immagini con tutti i colori rispondono . Trovato originale sul suo blog .

B. media (62, 71, 73)

Yokohama . Fornito da Geobits .

Media C. (115, 112, 111)

Tokyo . Fornito da Geobits .

D. media (154, 151, 154)

Cascata di Escher . Originale .

E. media (105, 103, 102)

Monte Shasta . Fornito da me.

F. media (75, 91, 110)

La notte stellata

Appunti

  • Gli esatti formati di input e output e i tipi di file immagine utilizzati dal tuo programma non contano molto. Assicurati solo che sia chiaro come usare il tuo programma.
  • Probabilmente è una buona idea (ma non tecnicamente un requisito) che se un'immagine ha già il colore medio obiettivo, dovrebbe essere prodotta così com'è.
  • Pubblica le immagini di prova con input di colore medio come (150, 100, 100) o (75, 91, 110), in modo che gli elettori possano vedere gli stessi input in soluzioni diverse. (Pubblicare più esempi di questo va bene, anche incoraggiato.)

2
I partecipanti possono scegliere i colori di input che usano per dimostrare l'efficacia della loro soluzione? Ciò non rende difficile per le persone confrontare le soluzioni? In casi estremi, qualcuno potrebbe scegliere colori di input molto simili alla media dell'immagine e sembrerebbe che la loro soluzione sia molto efficace.
Reto Koradi,

1
@ vihan1086 Se ho capito bene, il colore medio viene fornito come input di colore RGB a 24 bit, non trovato da un'immagine di input.
trichoplax,

3
Potrebbe essere interessante utilizzare l'interpretazione di @ vihan1086 e utilizzare le immagini di esempio come fonte dei colori di input, in modo che un'immagine venga visualizzata nel colore medio di un'altra. In questo modo è possibile confrontare equamente risposte diverse.
trichoplax,

Il problema principale è che la maggior parte di loro ha una media molto simile al grigio. La Notte stellata è probabilmente la più lontana da ciò, ma il resto è piuttosto piatto.
Geobits il

@RetoKoradi Speriamo che gli elettori siano abbastanza intelligenti da tenere conto di queste cose, anche se ho aggiunto una nota su quali colori medi predefiniti usare.
Calvin's Hobbies,

Risposte:


11

Python 2 + PIL, semplice ridimensionamento del colore

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)

Ecco un approccio ingenuo che dovrebbe servire da buona base. Ad ogni iterazione, confrontiamo la nostra media attuale con la media desiderata e ridimensioniamo l'RGB di ciascun pixel in base al rapporto corrispondente. Dobbiamo stare un po 'attenti, per due motivi:

  • Il ridimensionamento 0 risulta ancora in 0, quindi prima di ridimensionare aggiungiamo qualcosa di piccolo (qui 0.01 )

  • I valori RGB sono compresi tra 0 e 255, quindi è necessario regolare il rapporto di conseguenza per compensare il fatto che il ridimensionamento dei pixel con limite non fa nulla.

Le immagini vengono salvate come PNG perché il salvataggio come JPG sembra incasinare le medie dei colori.

Uscita campione

(40, 40, 40)

inserisci qui la descrizione dell'immagine inserisci qui la descrizione dell'immagine inserisci qui la descrizione dell'immagine inserisci qui la descrizione dell'immagine inserisci qui la descrizione dell'immagine inserisci qui la descrizione dell'immagine

(150, 100, 100)

inserisci qui la descrizione dell'immagine inserisci qui la descrizione dell'immagine inserisci qui la descrizione dell'immagine inserisci qui la descrizione dell'immagine inserisci qui la descrizione dell'immagine inserisci qui la descrizione dell'immagine

(75, 91, 110), palette Notte stellata

inserisci qui la descrizione dell'immagine inserisci qui la descrizione dell'immagine inserisci qui la descrizione dell'immagine inserisci qui la descrizione dell'immagine inserisci qui la descrizione dell'immagine inserisci qui la descrizione dell'immagine


2
Per questo, vuoi sicuramente utilizzare un formato immagine con compressione non lossy. Quindi JPEG non è una buona opzione.
Reto Koradi,

Puoi sempre contare su Sp per fantastiche soluzioni di sfida delle immagini.
Alex A.

6

C ++, correzione gamma

Ciò esegue una regolazione della luminosità dell'immagine usando una semplice correzione gamma, con il valore gamma determinato separatamente per ciascun componente in modo che corrisponda alla media target.

I passaggi di alto livello sono:

  1. Leggi l'immagine ed estrai l'istogramma per ogni componente di colore.
  2. Eseguire una ricerca binaria del valore gamma per ciascun componente. Viene eseguita una ricerca binaria sui valori gamma, fino a quando l'istogramma risultante ha la media desiderata.
  3. Leggi l'immagine una seconda volta e applica la correzione gamma.

Tutti gli input / output delle immagini utilizzano file PPM in ASCII. Le immagini sono state convertite da / in PNG usando GIMP. Il codice è stato eseguito su un Mac, le conversioni di immagini sono state eseguite su Windows.

Codice:

#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;
}

Il codice stesso è abbastanza semplice. Un dettaglio sottile ma importante è che, mentre i valori di colore sono nell'intervallo [0, 255], li mappa sulla curva gamma come se l'intervallo fosse [-1, 256]. Ciò consente di forzare la media su 0 o 255. Altrimenti, 0 rimarrebbe sempre 0 e 255 rimarrebbe sempre 255, il che potrebbe non consentire mai una media di 0/255.

Usare:

  1. Salvare il codice in un file con estensione .cpp, ad esforce.cpp .
  2. Compila con c++ -o force -O2 force.cpp .
  3. Corri con ./force input.ppm targetR targetG target >output.ppm.

Uscita campione per 40, 40, 40

Si noti che le immagini per tutti i campioni più grandi sono incluse come JPEG poiché superano il limite di dimensione SE come PNG. Poiché JPEG è un formato di compressione con perdita, potrebbero non corrispondere esattamente alla media target. Ho la versione PNG di tutti i file, che corrisponde esattamente.

af1 BF1 CF1 df1 EF1 FF1

Uscita campione per 150, 100, 100:

af2 BF2 CF2 DF2 EF2 FF2

Uscita campione per 75, 91, 110:

af3 BF3 CF3 DF3 EF3 FF3


Ho dovuto ridurre le altre immagini per raggiungere il limite - forse provarci?
Sp3000,

@ Sp3000 Ok, ora ho incluso tutte le immagini. Anche con le miniature adesso. Ho finito per usare la versione JPEG per quelli grandi. In realtà, uno di questi era al di sotto del limite di dimensioni, ma sembra che sia stato convertito automaticamente in JPEG. Il primo e l'ultimo esempio sono ancora PNG.
Reto Koradi,

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)

Ciò scorre attraverso ogni pixel in un ordine casuale e riduce la distanza tra ciascun componente del colore del pixel e 255o 0(a seconda che la media corrente sia inferiore o maggiore della media desiderata). La distanza è ridotta di un fattore moltiplicativo fisso. Questo si ripete fino ad ottenere la media desiderata. La riduzione è sempre almeno 1, a meno che il colore non sia 255(o 0), per garantire che l'elaborazione non si arresti quando il pixel è vicino al bianco o al nero.

Uscita campione

(40, 40, 40)

inserisci qui la descrizione dell'immagine inserisci qui la descrizione dell'immagine inserisci qui la descrizione dell'immagine inserisci qui la descrizione dell'immagine inserisci qui la descrizione dell'immagine inserisci qui la descrizione dell'immagine

(150, 100, 100)

inserisci qui la descrizione dell'immagine inserisci qui la descrizione dell'immagine inserisci qui la descrizione dell'immagine inserisci qui la descrizione dell'immagine inserisci qui la descrizione dell'immagine inserisci qui la descrizione dell'immagine

(75, 91, 110)

inserisci qui la descrizione dell'immagine inserisci qui la descrizione dell'immagine inserisci qui la descrizione dell'immagine inserisci qui la descrizione dell'immagine inserisci qui la descrizione dell'immagine inserisci qui la descrizione dell'immagine


1

Giava

Un approccio basato su RNG. Un po 'lento per immagini di input di grandi dimensioni.

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"));
    }
}

test:

(40,40,40)

inserisci qui la descrizione dell'immagineinserisci qui la descrizione dell'immagineinserisci qui la descrizione dell'immagineinserisci qui la descrizione dell'immagineinserisci qui la descrizione dell'immagineinserisci qui la descrizione dell'immagine

(150.100.100)

inserisci qui la descrizione dell'immagineinserisci qui la descrizione dell'immagineinserisci qui la descrizione dell'immagineinserisci qui la descrizione dell'immagineinserisci qui la descrizione dell'immagineinserisci qui la descrizione dell'immagine

(75,91,110)

inserisci qui la descrizione dell'immagineinserisci qui la descrizione dell'immagineinserisci qui la descrizione dell'immagineinserisci qui la descrizione dell'immagineinserisci qui la descrizione dell'immagineinserisci qui la descrizione dell'immagine

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.