이미지에 평균을 강제 은 실제로 각 색상 채널마다

표준 트루 컬러 이미지와 단일 24 비트 RGB 색상 (0에서 255까지의 3 개의 숫자) 을 취하는 프로그램을 작성하십시오 . 평균 색상 이 정확히 입력 된 단일 색상이되도록 입력 이미지를 수정하거나 동일한 크기의 새 이미지를 출력하십시오 . 입력 이미지의 픽셀을 원하는 방식으로 수정할 수 있지만 가능한 한 시각적으로 눈에 띄지 않게 색상을 변경하는 것이 목표입니다 .

RGB 이미지 의 평균 색상 은 실제로 각 색상 채널마다 하나씩 세 개의 산술 수단 세트입니다 . 평균 빨간색 값은 이미지의 모든 픽셀에 대한 빨간색 값의 합계를 총 픽셀 수 (이미지 영역)로 나눈 값을 가장 가까운 정수로 내림합니다. 녹색과 파란색 평균은 같은 방식으로 계산됩니다.

Python 2 ( PIL 포함 ) 스크립트는 대부분의 이미지 파일 형식의 평균 색상을 계산할 수 있습니다.

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)

(이 유사한 평균 색 프로그램은 여기에 있지만 반드시 동일한 계산을하지 않습니다.)

프로그램의 주요 요구 사항에 대한 것입니다 어떤 입력 이미지의 해당 출력의 평균 색이 있어야 정확히 파이썬 조각 또는 일부 해당 코드에 의해 판단으로 – 입력했다 색상을 일치합니다. 출력 이미지는 입력 이미지와 크기가 동일해야합니다.

당신은 기술적으로 프로그램을 제출 수 있도록 단순히 색상 전체 입력 지정된 평균 색 (평균은 항상 그 색을 것이기 때문에), 그러나 이것은이다 인기 투표 – 이길 것이다 가장 많은 표 제출 , 그러한 사소한 제출은 많은 공감대를 얻을 수 없습니다. 휴먼 비전에서 기발한 점을 활용하거나 이미지를 축소하고 그 주위에 색 테두리를 그리는 것과 같은 새로운 아이디어는 (희망스럽게) 투표하게됩니다.

평균 색상과 이미지의 특정 조합은 매우 눈에 띄는 색상 변경이 필요합니다. 경기에 평균 색이 검은 색이라면 예를 들어, (0, 0, 0), 어떤 어떤 픽셀이 아닌 0 값이 있다면, 그들이 (평균 비제도를 만들 것 때문에 입력 이미지가 필요 완전히 검은 만들어 질 반올림 오류 방지). 투표 할 때 이러한 제한 사항을 명심하십시오.

테스트 이미지

일부 이미지와 기본 평균 색상으로 재생할 수 있습니다. 전체 크기를 보려면 클릭하십시오.

A. 평균 (127, 127, 127)

에서 fejesjoco모든 색상과 이미지는 대답 . 그의 블로그 에서 원본 을 찾았 습니다 .

B. 평균 (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) 인 테스트 이미지를 게시하면 유권자들이 다른 솔루션에서 동일한 입력을 볼 수 있습니다. (이보다 더 많은 예제를 게시하는 것도 좋습니다.


답변

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 사이이므로 캡핑 된 픽셀을 스케일링해도 아무런 영향을 미치지 않도록 비율을 조정해야합니다.

JPG로 저장하면 색상 평균이 엉망이되어 이미지가 PNG로 저장됩니다.

샘플 출력

(40, 40, 40)






(150, 100, 100)






(75, 91, 110), 별이 빛나는 밤 팔레트







답변

C ++, 감마 보정

이는 간단한 감마 보정을 사용하여 이미지의 밝기를 조정하며 각 성분에 대해 감마 값이 대상 평균과 일치하도록 별도로 결정됩니다.

높은 수준의 단계는 다음과 같습니다.

  1. 각 색상 구성 요소에 대한 이미지를 읽고 히스토그램을 추출하십시오.
  2. 각 구성 요소에 대한 감마 값의 이진 검색을 수행하십시오. 결과 히스토그램이 원하는 평균을 가질 때까지 감마 값에 대한 이진 검색이 수행됩니다.
  3. 이미지를 다시 읽고 감마 보정을 적용하십시오.

모든 이미지 입 / 출력은 ASCII의 PPM 파일을 사용합니다. 김프를 사용하여 이미지를 PNG에서 PNG로 변환했습니다. 코드는 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의 샘플 출력

더 큰 모든 샘플의 이미지는 PNG로 SE 크기 제한을 초과하므로 JPEG로 포함됩니다. JPEG는 손실 압축 형식이므로 목표 평균과 정확히 일치하지 않을 수 있습니다. 모든 파일의 PNG 버전이 있는데 정확히 일치합니다.






150, 100, 100의 샘플 출력 :






75, 91, 110의 샘플 출력 :







답변

파이썬 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)







답변

자바

RNG 기반 접근법. 큰 입력 이미지의 경우 약간 느립니다.

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,100,100)

(75,91,110)