Paksa Rata-Rata pada Gambar

20

Menulis sebuah program yang dibutuhkan dalam standar truecolor gambar dan satu 24-bit RGB warna (tiga angka 0 sampai 255). Ubah gambar input (atau hasilkan gambar baru dengan dimensi yang sama) sedemikian rupa sehingga warna rata - ratanya sama dengan warna tunggal yang dimasukkan. Anda dapat memodifikasi piksel pada gambar input dengan cara apa pun yang Anda inginkan untuk mencapai hal ini, tetapi tujuannya adalah untuk membuat perubahan warna sedapat mungkin tidak terlihat secara visual .

Warna rata - rata gambar RGB sebenarnya adalah satu set dari tiga cara aritmatika , satu untuk setiap saluran warna. Nilai merah rata-rata adalah jumlah dari nilai merah di semua piksel dalam gambar dibagi dengan jumlah total piksel (area gambar), dibulatkan ke bawah ke bilangan bulat terdekat. Rata-rata hijau dan biru dihitung dengan cara yang sama.

Ini Python 2 (dengan PIL ) naskah dapat menghitung warna rata-rata sebagian besar format file gambar:

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)

(Ada program warna rata-rata serupa di sini , tetapi mereka tidak harus melakukan perhitungan yang persis sama.)

Persyaratan utama untuk program Anda adalah bahwa untuk gambar input apa pun , warna rata-rata output yang sesuai harus benar - benar cocok dengan warna yang dimasukkan - seperti yang dinilai oleh cuplikan Python atau beberapa kode yang setara. Gambar output juga harus memiliki dimensi yang sama persis dengan gambar input.

Jadi, Anda bisa secara teknis mengirimkan program yang hanya mewarnai seluruh input warna rata-rata yang ditentukan (karena rata-rata akan selalu menjadi warna itu), tetapi ini adalah kontes popularitas - pengiriman dengan jumlah suara terbanyak akan menang , dan sepele seperti itu pengiriman tidak akan membuat Anda banyak upvotes. Ide-ide baru seperti memanfaatkan kebiasaan aneh dalam penglihatan manusia, atau mengecilkan gambar dan menggambar batas berwarna di sekitarnya akan (semoga) memberi Anda suara.

Perhatikan bahwa kombinasi warna dan gambar rata-rata tertentu memerlukan perubahan warna yang sangat mencolok. Misalnya, jika warna rata-rata yang cocok adalah hitam (0, 0, 0), gambar input apa pun harus dibuat benar-benar hitam karena jika setiap piksel memiliki nilai bukan nol, mereka akan membuat rata-rata juga bukan nol ( cegah kesalahan pembulatan). Ingatlah batasan seperti itu saat memberikan suara.

Gambar Uji

Beberapa gambar dan warna rata-rata default untuk dimainkan. Klik untuk ukuran penuh.

A. rata-rata (127, 127, 127)

Dari fejesjoco 's Gambar dengan semua warna menjawab . Ditemukan orisinal di blog-nya .

B. rata (62, 71, 73)

Yokohama . Disediakan oleh Geobits .

C. Rata-rata (115, 112, 111)

Tokyo . Disediakan oleh Geobits .

D. rata-rata (154, 151, 154)

Air Terjun Escher . Asli .

E. rata-rata (105, 103, 102)

Gunung Shasta . Disediakan oleh saya.

F. rata-rata (75, 91, 110)

Malam berbintang

Catatan

  • Input dan output format dan tipe file gambar yang tepat yang digunakan program Anda tidak menjadi masalah. Pastikan saja jelas bagaimana menggunakan program Anda.
  • Mungkin ide yang bagus (tetapi bukan persyaratan teknis) bahwa jika gambar sudah memiliki warna rata-rata tujuan, itu harus menjadi output apa adanya.
  • Silakan memposting gambar uji dengan input warna rata-rata baik (150, 100, 100) atau (75, 91, 110), sehingga pemilih dapat melihat input yang sama di seluruh solusi yang berbeda. (Memposting lebih banyak contoh daripada ini tidak masalah, bahkan dianjurkan.)
Hobi Calvin
sumber
2
Peserta dapat memilih warna input yang mereka gunakan untuk menunjukkan efektivitas solusi mereka? Bukankah itu menyulitkan orang untuk membandingkan solusi? Dalam kasus ekstrim, seseorang dapat memilih warna input yang sangat mirip dengan rata-rata gambar, dan itu akan terlihat seperti solusi mereka sangat efektif.
Reto Koradi
1
@ vihan1086 Jika saya mengerti dengan benar, warna rata-rata disediakan sebagai input warna RGB 24 bit, tidak ditemukan dari gambar input.
trichoplax
3
Mungkin menarik untuk menggunakan interpretasi @ vihan1086, dan menggunakan contoh gambar sebagai sumber warna input sehingga satu gambar ditampilkan dalam warna rata-rata yang lain. Dengan cara ini, jawaban yang berbeda dapat dibandingkan secara adil.
trichoplax
Masalah utama dengan itu adalah kebanyakan dari mereka memiliki rata-rata yang sangat dekat dengan abu-abu. Starry Night mungkin adalah yang terjauh dari itu, tetapi sisanya rata-rata cukup datar.
Geobits
@RetoKoradi Semoga pemilih akan cukup pintar untuk mempertimbangkan hal-hal seperti itu, meskipun saya telah menambahkan catatan tentang apa warna rata-rata default untuk digunakan.
Hobi Calvin

Jawaban:

11

Python 2 + PIL, penskalaan warna sederhana

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)

Berikut ini adalah pendekatan naif yang harus berfungsi sebagai dasar yang baik. Pada setiap iterasi, kami membandingkan rata-rata kami saat ini dengan rata-rata yang diinginkan, dan skala RGB dari setiap piksel dengan rasio yang sesuai. Kita harus sedikit berhati-hati, karena dua alasan:

  • Penskalaan 0 masih menghasilkan 0, jadi sebelum kami menskalakan kami menambahkan sesuatu yang kecil (di sini 0.01)

  • Nilai RGB adalah antara 0 dan 255, jadi kita perlu menyesuaikan rasio yang sesuai untuk menebus fakta bahwa penskalaan piksel yang dibatasi tidak melakukan apa-apa.

Gambar disimpan sebagai PNG karena menyimpan sebagai JPG tampaknya mengacaukan rata-rata warna.

Output sampel

(40, 40, 40)

masukkan deskripsi gambar di sini masukkan deskripsi gambar di sini masukkan deskripsi gambar di sini masukkan deskripsi gambar di sini masukkan deskripsi gambar di sini masukkan deskripsi gambar di sini

(150, 100, 100)

masukkan deskripsi gambar di sini masukkan deskripsi gambar di sini masukkan deskripsi gambar di sini masukkan deskripsi gambar di sini masukkan deskripsi gambar di sini masukkan deskripsi gambar di sini

(75, 91, 110), palet Starry Night

masukkan deskripsi gambar di sini masukkan deskripsi gambar di sini masukkan deskripsi gambar di sini masukkan deskripsi gambar di sini masukkan deskripsi gambar di sini masukkan deskripsi gambar di sini

Sp3000
sumber
2
Anda pasti ingin menggunakan format gambar dengan kompresi non-lossy untuk ini. Jadi JPEG bukanlah pilihan yang baik.
Reto Koradi
Anda selalu dapat mengandalkan Sp untuk solusi tantangan gambar keren.
Alex A.
6

C ++, koreksi gamma

Ini melakukan penyesuaian kecerahan gambar menggunakan koreksi gamma sederhana, dengan nilai gamma ditentukan secara terpisah untuk setiap komponen agar sesuai dengan rata-rata target.

Langkah-langkah tingkat tinggi adalah:

  1. Baca gambar dan ekstrak histogram untuk setiap komponen warna.
  2. Lakukan pencarian biner dari nilai gamma untuk setiap komponen. Pencarian biner dilakukan pada nilai gamma, sampai histogram yang dihasilkan memiliki rata-rata yang diinginkan.
  3. Baca gambar untuk kedua kalinya, dan terapkan koreksi gamma.

Semua input / output gambar menggunakan file PPM di ASCII. Gambar dikonversi dari / ke PNG menggunakan GIMP. Kode dijalankan pada Mac, konversi gambar dilakukan pada Windows.

Kode:

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

Kode itu sendiri cukup mudah. Satu detail halus tetapi penting adalah bahwa, sementara nilai warna berada dalam kisaran [0, 255], saya memetakannya ke kurva gamma seolah-olah kisarannya adalah [-1, 256]. Ini memungkinkan rata-rata dipaksa ke 0 atau 255. Jika tidak, 0 akan selalu tetap 0, dan 255 akan selalu tetap 255, yang mungkin tidak pernah memungkinkan untuk rata-rata 0/255.

Menggunakan:

  1. Simpan kode dalam file dengan ekstensi .cpp, mis force.cpp.
  2. Kompilasi dengan c++ -o force -O2 force.cpp.
  3. Jalankan dengan ./force input.ppm targetR targetG target >output.ppm.

Contoh output untuk 40, 40, 40

Perhatikan bahwa gambar untuk semua sampel yang lebih besar dimasukkan sebagai JPEG karena mereka melebihi batas ukuran SE sebagai PNG. Karena JPEG adalah format kompresi lossy, mereka mungkin tidak sama persis dengan rata-rata target. Saya memiliki versi PNG dari semua file, yang cocok persis.

Af1 Bf1 Cf1 Df1 Ef1 Ff1

Output sampel untuk 150, 100, 100:

Af2 Bf2 Cf2 Df2 Ef2 Ff2

Contoh output untuk 75, 91, 110:

Af3 Bf3 Cf3 Df3 Ef3 Ff3

Reto Koradi
sumber
Saya harus mengecilkan gambar lain untuk memenuhi batas - mungkin coba itu?
Sp3000
@ Sp3000 Ok, dapatkan semua gambar termasuk sekarang. Juga dengan thumbnail sekarang. Saya akhirnya menggunakan versi JPEG untuk yang besar. Sebenarnya, salah satu dari mereka berada di bawah batas ukuran, tetapi sepertinya dikonversi secara otomatis ke JPEG. Contoh pertama dan terakhir masih 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)

Ini beralih melalui setiap piksel dalam urutan acak, dan mengurangi jarak antara setiap komponen warna piksel dan 255atau 0(tergantung pada apakah rata-rata saat ini kurang atau lebih besar dari rata-rata yang diinginkan). Jarak dikurangi oleh faktor multiplikasi tetap. Ini diulangi sampai rata-rata yang diinginkan diperoleh. Pengurangan selalu setidaknya 1, kecuali warnanya 255(atau 0), untuk memastikan bahwa pemrosesan tidak terhenti begitu pikselnya mendekati putih atau hitam.

Output sampel

(40, 40, 40)

masukkan deskripsi gambar di sini masukkan deskripsi gambar di sini masukkan deskripsi gambar di sini masukkan deskripsi gambar di sini masukkan deskripsi gambar di sini masukkan deskripsi gambar di sini

(150, 100, 100)

masukkan deskripsi gambar di sini masukkan deskripsi gambar di sini masukkan deskripsi gambar di sini masukkan deskripsi gambar di sini masukkan deskripsi gambar di sini masukkan deskripsi gambar di sini

(75, 91, 110)

masukkan deskripsi gambar di sini masukkan deskripsi gambar di sini masukkan deskripsi gambar di sini masukkan deskripsi gambar di sini masukkan deskripsi gambar di sini masukkan deskripsi gambar di sini

es1024
sumber
1

Jawa

Pendekatan berbasis RNG. Agak lambat untuk gambar input besar.

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

Tes:

(40,40,40)

masukkan deskripsi gambar di sinimasukkan deskripsi gambar di sinimasukkan deskripsi gambar di sinimasukkan deskripsi gambar di sinimasukkan deskripsi gambar di sinimasukkan deskripsi gambar di sini

(150.100.100)

masukkan deskripsi gambar di sinimasukkan deskripsi gambar di sinimasukkan deskripsi gambar di sinimasukkan deskripsi gambar di sinimasukkan deskripsi gambar di sinimasukkan deskripsi gambar di sini

(75,91.110)

masukkan deskripsi gambar di sinimasukkan deskripsi gambar di sinimasukkan deskripsi gambar di sinimasukkan deskripsi gambar di sinimasukkan deskripsi gambar di sinimasukkan deskripsi gambar di sini

SuperJedi224
sumber