Gri Tonlamalı Görüntü


23

Kendi algoritmanızla gri tonlamalı bir görüntüyü saf siyah beyaza dönüştürün.

Yönergeler: Kendi yeni algoritmanızla gelmelisiniz. Önceden var olan algoritmaları (ör. Floyd-Steinburg) kullanamazsınız, ancak genel tekniği kullanabilirsiniz. Programınız bir görüntüyü okuyabilmeli ve aynı boyutta bir görüntü üretebilmelidir. Bu bir popülerlik yarışmasıdır, bu nedenle en iyisini üreten (orijinaline en yakın olan) ve en yaratıcı olan (oylarla belirlenen) kazanır. Kod kısa ise Bonus, gerekli olmasa da.

Girdi olarak istediğiniz gri tonlamalı resmi kullanabilirsiniz, 300x300'den büyük olmalıdır. Herhangi bir dosya formatı iyi.

Örnek giriş:

köpek yavrusu

Örnek çıktı:

sarsılan

Bu oldukça iyi bir iş, ama hala görünen çizgiler ve desenler var.


4
İlginç bir meydan okuma için +1, ancak bunun bir kod kodu (özellikli) veya başka bir tamamen nesnel kriter olarak çok daha iyi olacağını düşünüyorum.
Doorknob

2
Kod boyutu, hız ve bellek kullanımı ile ilgili sorun, cevabın geçerli olması için sonucun ne kadar kabul edilebilir olması gerektiği konusunda objektif bir eşiğe ihtiyaç duymanızdır, bu da oldukça imkansızdır. Popülerlik yarışması anlamlıdır, ancak kodda herhangi bir kısıtlama olmadan insanların kutudan düşünmeleri için hiçbir teşvik yoktur. Var olan bir algoritmayı uyguladığı için en iyi sonucu vermekten çok akıllıca bir cevap vermeyi tercih ederim. Ama şu anda ikincisini teşvik ediyorsun.
Martin Ender

3
Bir algoritma ile tekniği arasındaki çizgi, hangi tarafın düşeceğini belirlemek için çok incedir.
Peter Taylor,

2
Hepsi aynı görüntüden sonuçlar gösterse, sonuçları karşılaştırmanın çok daha kolay olacağını düşünüyorum.
joeytwiddle

3
Resmin kaynağını ekleyebilir misiniz? (Birinin resmini burada gördüğüne kızacağını sanmıyorum, ama kaynağa değinmek adil olur)
AL

Yanıtlar:


16

Fortran

Tamam, astronomi için kullanılan FITS adlı belirsiz bir görüntü formatı kullanıyorum. Bu, bu tür görüntüleri okumak ve yazmak için bir Fortran kütüphanesi olduğu anlamına gelir. Ayrıca, ImageMagick ve Gimp, FITS görüntüleri okuyabilir / yazabilir.

Kullandığım algoritma "Sierra Lite" titremesine dayanıyor, ancak iki geliştirmeye dayanıyor:
a) Yayılan hatayı 4/5 faktörü ile düşürüyorum.
b) Difüzyon matrisinde toplamını sabit tutarken rastgele bir değişkenlik ortaya koyarım.
Birlikte bunlar OP'lerin örneğinde görülen kalıpları neredeyse tamamen ortadan kaldırır.

CFITSIO kütüphanesinin kurulu olduğunu varsayarak, derlemek

gfortran-lcfitsio dither.f90

Dosya adları zor kodlanmıştır (bunu düzeltmek için rahatsız edilemez).

Kod:

program dither
  integer              :: status,unit,readwrite,blocksize,naxes(2),nfound
  integer              :: group,npixels,bitpix,naxis,i,j,fpixel,un
  real                 :: nullval,diff_mat(3,2),perr
  real, allocatable    :: image(:,:), error(:,:)
  integer, allocatable :: seed(:)
  logical              :: anynull,simple,extend
  character(len=80)    :: filename

  call random_seed(size=Nrand)
  allocate(seed(Nrand))
  open(newunit=un,file="/dev/urandom",access="stream",&
       form="unformatted",action="read",status="old")
  read(un) seed
  close(un)
  call random_seed(put=seed)
  deallocate(seed)

  status=0
  call ftgiou(unit,status)
  filename='PUPPY.FITS'
  readwrite=0
  call ftopen(unit,filename,readwrite,blocksize,status)
  call ftgknj(unit,'NAXIS',1,2,naxes,nfound,status)
  call ftgidt(unit,bitpix,status)
  npixels=naxes(1)*naxes(2)
  group=1
  nullval=-999
  allocate(image(naxes(1),naxes(2)))
  allocate(error(naxes(1)+1,naxes(2)+1))
  call ftgpve(unit,group,1,npixels,nullval,image,anynull,status)
  call ftclos(unit, status)
  call ftfiou(unit, status)

  diff_mat=0.0
  diff_mat(3,1) = 2.0 
  diff_mat(1,2) = 1.0
  diff_mat(2,2) = 1.0
  diff_mat=diff_mat/5.0

  error=0.0
  perr=0
  do j=1,naxes(2)
    do i=1,naxes(1)
      p=max(min(image(i,j)+error(i,j),255.0),0.0)
      if (p < 127.0) then
        perr=p
        image(i,j)=0.0
      else
        perr=p-255.0
        image(i,j)=255.0
      endif
      call random_number(r)
      r=0.6*(r-0.5)
      error(i+1,j)=  error(i+1,j)  +perr*(diff_mat(3,1)+r)
      error(i-1,j+1)=error(i-1,j+1)+perr*diff_mat(1,2)
      error(i  ,j+1)=error(i ,j+1) +perr*(diff_mat(2,2)-r)
    end do
  end do

  call ftgiou(unit,status)
  blocksize=1
  filename='PUPPY-OUT.FITS'
  call ftinit(unit,filename,blocksize,status)
  simple=.true.
  naxis=2
  extend=.true.
  call ftphpr(unit,simple,bitpix,naxis,naxes,0,1,extend,status)
  group=1
  fpixel=1
  call ftppre(unit,group,fpixel,npixels,image,status)
  call ftclos(unit, status)
  call ftfiou(unit, status)

  deallocate(image)
  deallocate(error)
end program dither

OPs postasında köpek yavrusu görüntüsü için
Yavru köpek resmi
örnek çıktı: OPs örnek çıktısı:
OPs köpek yavrusu dithered resmi


Bu gerçekten iyi görünüyor, kalite için rakipsiz olabilir
aditsu

Teşekkürler! Bunun rakipsiz olduğunu bilmiyorum, ancak bunu diğer iyi algoritmalara karşı yargılamak zor olacak (çok öznel).
yarı-extrinsik

1
Geriye dönük uyumluluğu kötüye kullanarak golf kodunu biliyorum, ancak aslında standart olarak kötüye kullanıyorsunuz. Bu kod aslında beni ağlatıyor.
Kyle Kanos,

@KyleKanos Kodum birisini ağlattığında her zaman mutlu oluyorum: p Bununla birlikte, burada özellikle ne korkunç? Evet, "örtük hiçbirini" kullanabilirdim, ama bunun neresinde eğlenceli? İşyerinde ciddi kodlama için kullanıyorum, ancak golf oynamak için kullanmıyorum. Ve kesinlikle CFITSIO kütüphane API'sinin tamamen korkunç olduğuna katılıyorum (ftppre () tek bir gerçek hassasiyette bir FITS görüntü veriyor, ftpprj () çift tamsayı hassasiyetinde bir görüntü veriyor, vb.) Ama bu F77 sizin için geriye dönük uyumluluktur.
yarı extrinsik

1
Tamam, bu yüzden çoğu benim sadece özensiz olduğum içindi. Geliştirdim. Yapıcı eleştiri her zaman takdir edilir :)
yarı dışsal

34

GraphicsMagick / ImageMagick

Sipariş edilen İkisi:

magick B2DBy.jpg -gamma .45455 -ordered-dither [all] 4x4 ordered4x4g45.pbm

Bir "yerleşik algoritma" kullanmamdan şikayet etmeden önce, lütfen algoritmayı bu uygulamalarda uyguladığımı göreceğiniz Nisan 2003 için GraphicsMagick ve ImageMagick için ChangeLog'u okuyun. Ayrıca, "-gamma .45455" ile "-diğerli-dither" kombinasyonu yenidir.

"-Gamma .45455" görüntünün çok açık olmasını önemser. "All" parametresi sadece GraphicsMagick ile gereklidir.

Bantlama var çünkü 4x4 sıralı görüntüde yalnızca 17 graylev var. Bantlama görünümü, 65 seviyeye sahip 8x8 sıralı dither kullanılarak azaltılabilir.

Orijinal görüntü, 4x4 ve 8x8 sıralı dithered çıktı ve rastgele eşik çıktı: görüntü tanımını buraya girin

Sipariş edilen taklidi sürümünü tercih ediyorum, ancak tamlık için rastgele eşikli sürümü de dahil ediyorum.

magick B2DBy.jpg -gamma .6 -random-threshold 10x90% random-threshold.pbm

"% 10x90", bu bölgelerde birkaç yalnız lekelenmekten kaçınmak için yüzde 10'un altındaki yoğunluk piksellerinin saf siyah ve yüzde 90'ın üzerindeki saf beyaz kılmak anlamına gelir.

Muhtemelen her ikisinin de olabildiğince hafıza açısından verimli olduğuna dikkat etmek önemlidir. Herhangi bir difüzyon da olmaz, bu nedenle, sıralı dither blokları yazarken bile, her seferinde bir piksel çalışırlar ve komşu pikseller hakkında hiçbir şey bilmeleri gerekmez. ImageMagick ve GraphicsMagick her seferinde bir satırı işler, ancak bu yöntemler için gerekli değildir. Sıralı taklidi dönüşümleri eski x86_64 bilgisayarımda gerçek saniyenin 0,6 saniyesinden az sürüyor.


31
"" Yerleşik bir algoritma "kullanmamdan şikayet etmeden önce, lütfen algoritmayı bu uygulamalarda uyguladığımı göreceğiniz Nisan 2003 için GraphicsMagick ve ImageMagick için ChangeLog'u okuyun." Sırf yanak için +1.
Joe Z.

22

Kod stili için özür dilerim, bunu java sınıfımda yeni oluşturduğumuz bazı kütüphaneleri kullanarak bir araya getirdim ve kötü bir kopyala ve yapıştır sayıları var. Algoritma, görüntüdeki rasgele dikdörtgenleri seçer ve dithered görüntüdeki veya orijinal görüntüdeki ortalama parlaklığın daha büyük olup olmadığını kontrol eder. Ardından, parlaklıkları çizgiye yaklaştırmak için bir pikseli açar veya kapatır, tercihen orijinal görüntüden daha farklı pikselleri seçer. Bence, yavru köpeğin saçı gibi ince detayları ortaya çıkarmak daha iyi bir iş çıkarsa da, görüntü daha gürültülü çünkü hiç olmadığı alanlarda bile detayları ortaya çıkarmaya çalışıyor.

görüntü tanımını buraya girin

public void dither(){
    int count = 0;
    ditheredFrame.suspendNotifications();
    while(count < 1000000){
        count ++;
        int widthw = 5+r.nextInt(5);
        int heightw = widthw;
        int minx = r.nextInt(width-widthw);
        int miny = r.nextInt(height-heightw);



            Frame targetCropped = targetFrame.crop(minx, miny, widthw, heightw);
            Frame ditherCropped = ditheredFrame.crop(minx, miny, widthw, heightw);

            if(targetCropped.getAverage().getBrightness() > ditherCropped.getAverage().getBrightness() ){
                int x = 0;
                int y = 0;
                double diff = 0;

                for(int i = 1; i < ditherCropped.getWidth()-1; i ++){
                    for(int j = 1; j < ditherCropped.getHeight()-1; j ++){
                        double d = targetCropped.getPixel(i,  j).getBrightness() - ditherCropped.getPixel(i, j).getBrightness();
                        d += .005* targetCropped.getPixel(i+1,  j).getBrightness() - .005*ditherCropped.getPixel(i+1, j).getBrightness();

                        d += .005* targetCropped.getPixel(i-1,  j).getBrightness() - .005*ditherCropped.getPixel(i-1, j).getBrightness();

                        d += .005* targetCropped.getPixel(i,  j+1).getBrightness() -.005* ditherCropped.getPixel(i, j+1).getBrightness();

                        d += .005* targetCropped.getPixel(i,  j-1).getBrightness() - .005*ditherCropped.getPixel(i, j-1).getBrightness();

                        if(d > diff){
                            diff = d;
                            x = i;
                            y = j;
                        }
                    }
                    ditherCropped.setPixel(x,  y,  WHITE);
                }

            } else {
                int x = 0;
                int y = 0;
                double diff = 0;

                for(int i = 1; i < ditherCropped.getWidth()-1; i ++){
                    for(int j = 1; j < ditherCropped.getHeight()-1; j ++){
                        double d =  ditherCropped.getPixel(i, j).getBrightness() -targetCropped.getPixel(i,  j).getBrightness();
                        d += -.005* targetCropped.getPixel(i+1,  j).getBrightness() +.005* ditherCropped.getPixel(i+1, j).getBrightness();

                        d += -.005* targetCropped.getPixel(i-1,  j).getBrightness() +.005* ditherCropped.getPixel(i+1, j).getBrightness();

                        d += -.005* targetCropped.getPixel(i,  j+1).getBrightness() + .005*ditherCropped.getPixel(i, j+1).getBrightness();

                        d += -.005* targetCropped.getPixel(i,  j-1).getBrightness() + .005*ditherCropped.getPixel(i, j-1).getBrightness();



                        if(d > diff){
                            diff = d;
                            x = i;
                            y = j;
                        }
                    }
                    ditherCropped.setPixel(x,  y,  BLACK);
                }
            }


    }
    ditheredFrame.resumeNotifications();
}

Bunun deterministik olduğunu kabul ediyorum? Eğer öyleyse, ne kadar hızlı?
Büyük

Bu rastgele ve bilgisayarımda yaklaşık 3 saniye sürer.
QuadmasterXLII

2
Belki de en yüksek doğrulukta algoritma olmasa da, sonuçlar kendi başına sanattır.
AJMansfield

4
Bu algoritmanın görünüşünü gerçekten seviyorum! Ama bence kısmen çok iyi görünüyor çünkü kürkle kabaca benzer bir doku üretiyor ve bu da kürklü bir hayvan. Ancak bunun doğru olduğundan tam olarak emin değilim. Örneğin bir arabanın başka bir görüntüsünü gönderebilir misiniz?
yarı-dışsal

1
Bunun hem algoritmanın özgünlüğü hem de sonuçların uygunsuzluğu açısından en iyi cevap olduğunu düşünüyorum. Ayrıca başka görüntülerde de çalışmasını görmek isterdim.
Nathaniel,

13

Ghostscript (ImageMagick'in yardımı ile)

Benim 'kendi yeni algoritmam' olmaktan uzak, ama üzgünüm, buna karşı koyamadım.

convert puppy.jpg puppy.pdf && \
convert puppy.jpg -depth 8 -colorspace Gray -resize 20x20! -negate gray:- | \
gs -q -sDEVICE=ps2write -o- -c \
    '<</Thresholds (%stdin) (r) file 400 string readstring pop 
       /HalftoneType 3 /Width 20 /Height 20
     >>sethalftone' \
    -f puppy.pdf | \
gs -q -sDEVICE=pngmono -o puppy.png -

görüntü tanımını buraya girin

Elbette 'aynı boyutta' bir kısıtlama olmadan daha iyi çalışır.


2
Bu komik. Warhol tarzı mucize hakkında hiç kimsenin yorum yapmamasından şaşırdım.
Andreï Kostyrka,

10

JAVA

İşte benim gönderim. Bir JPG resmi çeker, piksel parlaklığı başına piksel hesaplar ( bu SO sorusundaki Bonan sayesinde ) ve sonra elde edilen pikselin siyah mı yoksa beyaz mı olacağını bilmek için rastgele bir düzende kontrol edin. Darkerst pikselleri her zaman siyah olacak ve en parlak pikseller görüntü ayrıntılarını korumak için her zaman beyaz olacaktır.

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Random;

import javax.imageio.ImageIO;

public class DitherGrayscale {

    private BufferedImage original;
    private double[] threshold = { 0.25, 0.26, 0.27, 0.28, 0.29, 0.3, 0.31,
            0.32, 0.33, 0.34, 0.35, 0.36, 0.37, 0.38, 0.39, 0.4, 0.41, 0.42,
            0.43, 0.44, 0.45, 0.46, 0.47, 0.48, 0.49, 0.5, 0.51, 0.52, 0.53,
            0.54, 0.55, 0.56, 0.57, 0.58, 0.59, 0.6, 0.61, 0.62, 0.63, 0.64,
            0.65, 0.66, 0.67, 0.68, 0.69 };


    public static void main(String[] args) {
        DitherGrayscale d = new DitherGrayscale();
        d.readOriginal();
        d.dither();

    }

    private void readOriginal() {
        File f = new File("original.jpg");
        try {
            original = ImageIO.read(f);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void dither() {
        BufferedImage imRes = new BufferedImage(original.getWidth(),
                original.getHeight(), BufferedImage.TYPE_INT_RGB);
        Random rn = new Random();
        for (int i = 0; i < original.getWidth(); i++) {
            for (int j = 0; j < original.getHeight(); j++) {

                int color = original.getRGB(i, j);

                int red = (color >>> 16) & 0xFF;
                int green = (color >>> 8) & 0xFF;
                int blue = (color >>> 0) & 0xFF;

                double lum = (red * 0.21f + green * 0.71f + blue * 0.07f) / 255;

                if (lum <= threshold[rn.nextInt(threshold.length)]) {
                    imRes.setRGB(i, j, 0x000000);
                } else {
                    imRes.setRGB(i, j, 0xFFFFFF);
                }

            }
        }
        try {
            ImageIO.write(imRes, "PNG", new File("result.png"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

İşlenmiş görüntü

Diğer örnekler:

orijinal İşlenmiş

Ayrıca tam renkli görüntülerle çalışır:

Renkli görüntü Sonuç


9

CJam

lNl_~:H;:W;Nl;1N[{[{ri}W*]{2/{:+}%}:P~}H*]z{P}%:A;H{W{I2/A=J2/=210/[0X9EF]=I2%2*J2%+m>2%N}fI}fJ

95 bytes :) Hem giriş hem de çıkış
için ASCII PGM (P2) formatını yorum satırı olmadan kullanır .

Yöntem çok basit: 2 x 2 piksel kareler ekler, 0..4 aralığına dönüştürür, daha sonra 2 x 2 siyah beyaz pikseller üretmek için karşılık gelen 4 bit bir kalıp kullanır.
Bu aynı zamanda genişlik ve yüksekliğin eşit olması gerektiği anlamına gelir.

Numune:

deterministic köpek yavrusu

Ve sadece 27 baytta rastgele bir algoritma:

lNl_~*:X;Nl;1N{ri256mr>N}X*

Aynı dosya formatını kullanır.

Numune:

rastgele köpek yavrusu

Ve nihayet karma bir yaklaşım: dama tahtası desenine karşı önyargılı rasgele titreme; 44 bayt:

lNl_~:H;:W;Nl;1NH{W{ri128_mr\IJ+2%*+>N}fI}fJ

Numune:

karışık köpek


2
İlki, Nintendo DSi'nin "Flipnote Studio" uygulamasıyla karşılaştırılabilir.
BobTheAwesome

6

Java (1.4+)

Tekerleği burada yeniden icat edip etmediğimden emin değilim, ancak benzersiz olabileceğini düşünüyorum ...

sınırlı rastgele dizileri olan

Sınırlı rastgele dizileri ile

Saf rastgele titreme

Saf rastgele titreme

görüntü tanımını buraya girin

Averroes'in cevabından şehir görüntüsü

Algoritma, özellikleri korumak için lokal parlaklık enerjisi ve normalleştirme kavramını kullanır. İlk versiyon daha sonra, benzer parlaklığa sahip alanlar üzerinde farklı bir görünüm elde etmek için rastgele bir jitter kullandı. Ancak görsel olarak çekici değildi. Buna karşılık olarak, sınırlı sayıda rastgele dizi dizisi ham girdi piksel parlaklığına eşleştirilir ve numuneler yinelenen ve tekrar tekrar kullanılan dithered görünümlü arka planlar elde edilir.

import java.awt.image.BufferedImage;
import java.io.File;
import javax.imageio.ImageIO;

public class LocalisedEnergyDitherRepeatRandom {

    static private final float EIGHT_BIT_DIVISOR=1.0F/256;
    private static final int KERNEL_SIZE_DIV_2 =4;
    private static final double JITTER_MULTIPLIER=0.01;
    private static final double NO_VARIANCE_JITTER_MULTIPLIER=1.5;
    private static final int RANDOM_SEQUENCE_LENGTH=10;
    private static final int RANDOM_SEQUENCE_COUNT=20;
    private static final boolean USE_RANDOM_SEQUENCES=true;

    public static void main(String args[]) throws Exception
    {
        BufferedImage image = ImageIO.read(new File("data/dog.jpg"));
        final int width = image.getWidth();
        final int height = image.getHeight();
        float[][][] yuvImage =convertToYUVImage(image);
        float[][][] outputYUVImage = new float[width][height][3];
        double circularKernelLumaMean,sum,jitter,outputLuma,variance,inputLuma;
        int circularKernelPixelCount,y,x,kx,ky;
        double[][] randomSequences = new double[RANDOM_SEQUENCE_COUNT][RANDOM_SEQUENCE_LENGTH];
        int[] randomSequenceOffsets = new int[RANDOM_SEQUENCE_COUNT];

        // Generate random sequences
        for (y=0;y<RANDOM_SEQUENCE_COUNT;y++)
        {
            for (x=0;x<RANDOM_SEQUENCE_LENGTH;x++)
            {
                randomSequences[y][x]=Math.random();
            }
        }

        for (y=0;y<height;y++)
        {
            for (x=0;x<width;x++)
            {
                circularKernelLumaMean=0;
                sum=0;
                circularKernelPixelCount=0;

                /// calculate the mean of the localised surrounding pixels contained in 
                /// the area of a circle surrounding the pixel.
                for (ky=y-KERNEL_SIZE_DIV_2;ky<y+KERNEL_SIZE_DIV_2;ky++)
                {
                    if (ky>=0 && ky<height)
                    {
                        for (kx=x-KERNEL_SIZE_DIV_2;kx<x+KERNEL_SIZE_DIV_2;kx++)
                        {
                            if (kx>=0 && kx<width)
                            {
                                double distance= Math.sqrt((x-kx)*(x-kx)+(y-ky)*(y-ky));

                                if (distance<=KERNEL_SIZE_DIV_2)
                                {
                                    sum+=yuvImage[kx][ky][0];
                                    circularKernelPixelCount++;
                                }
                            }
                        }
                    }
                }

                circularKernelLumaMean=sum/circularKernelPixelCount;

                /// calculate the variance of the localised surrounding pixels contained in 
                /// the area of a circle surrounding the pixel.
                sum =0;

                for (ky=y-KERNEL_SIZE_DIV_2;ky<y+KERNEL_SIZE_DIV_2;ky++)
                {
                    if (ky>=0 && ky<height)
                    {
                        for (kx=x-KERNEL_SIZE_DIV_2;kx<x+KERNEL_SIZE_DIV_2;kx++)
                        {
                            if (kx>=0 && kx<width)
                            {
                                double distance= Math.sqrt((x-kx)*(x-kx)+(y-ky)*(y-ky));

                                if (distance<=KERNEL_SIZE_DIV_2)
                                {
                                    sum+=Math.abs(yuvImage[kx][ky][0]-circularKernelLumaMean);
                                }
                            }
                        }
                    }
                }

                variance = sum/(circularKernelPixelCount-1);

                if (variance==0)
                {
                    // apply some jitter to ensure there are no large blocks of single colour
                    inputLuma=yuvImage[x][y][0];
                    jitter = Math.random()*NO_VARIANCE_JITTER_MULTIPLIER;
                }
                else
                {
                    // normalise the pixel based on localised circular kernel
                    inputLuma = outputYUVImage[x][y][0]=(float) Math.min(1.0, Math.max(0,yuvImage[x][y][0]/(circularKernelLumaMean*2)));
                    jitter = Math.exp(variance)*JITTER_MULTIPLIER;
                }

                if (USE_RANDOM_SEQUENCES)
                {
                    int sequenceIndex =(int) (yuvImage[x][y][0]*RANDOM_SEQUENCE_COUNT);
                    int sequenceOffset = (randomSequenceOffsets[sequenceIndex]++)%RANDOM_SEQUENCE_LENGTH;
                    outputLuma=inputLuma+randomSequences[sequenceIndex][sequenceOffset]*jitter*2-jitter;
                }
                else
                {
                    outputLuma=inputLuma+Math.random()*jitter*2-jitter;
                }


                // convert 8bit luma to 2 bit luma
                outputYUVImage[x][y][0]=outputLuma<0.5?0:1.0f;
            }
        }

        renderYUV(image,outputYUVImage);
        ImageIO.write(image, "png", new File("data/dog.jpg.out.png"));
    }

      /**
       * Converts the given buffered image into a 3-dimensional array of YUV pixels
       * @param image the buffered image to convert
       * @return 3-dimensional array of YUV pixels
       */
      static private float[][][] convertToYUVImage(BufferedImage image)
      {
        final int width = image.getWidth();
        final int height = image.getHeight();
        float[][][] yuv = new float[width][height][3];
        for (int y=0;y<height;y++)
        {
          for (int x=0;x<width;x++)
          {
            int rgb = image.getRGB( x, y );
            yuv[x][y]=rgb2yuv(rgb);
          }
        }
        return yuv;
      }

      /**
       * Renders the given YUV image into the given buffered image.
       * @param image the buffered image to render to
       * @param pixels the YUV image to render.
       * @param dimension the
       */
      static private void renderYUV(BufferedImage image, float[][][] pixels)
      {
        final int height = image.getHeight();
        final int width = image.getWidth();
        int rgb;

        for (int y=0;y<height;y++)
        {
          for (int x=0;x<width;x++)
          {

            rgb = yuv2rgb( pixels[x][y] );
            image.setRGB( x, y,rgb );
          }
        }
      }

      /**
       * Converts a RGB pixel into a YUV pixel
       * @param rgb a pixel encoded as 24 bit RGB
       * @return array representing a pixel. Consisting of Y,U and V components
       */
      static float[] rgb2yuv(int rgb)
      {
        float red = EIGHT_BIT_DIVISOR*((rgb>>16)&0xFF);
        float green = EIGHT_BIT_DIVISOR*((rgb>>8)&0xFF);
        float blue = EIGHT_BIT_DIVISOR*(rgb&0xFF);

        float Y = 0.299F*red + 0.587F * green + 0.114F * blue;
        float U = (blue-Y)*0.565F;
        float V = (red-Y)*0.713F;

        return new float[]{Y,U,V};
      }

      /**
       * Converts a YUV pixel into a RGB pixel
       * @param yuv array representing a pixel. Consisting of Y,U and V components
       * @return a pixel encoded as 24 bit RGB
       */
      static int yuv2rgb(float[] yuv)
      {
        int red = (int) ((yuv[0]+1.403*yuv[2])*256);
        int green = (int) ((yuv[0]-0.344 *yuv[1]- 0.714*yuv[2])*256);
        int blue = (int) ((yuv[0]+1.77*yuv[1])*256);

        // clamp to 0-255 range
        red=red<0?0:red>255?255:red;
        green=green<0?0:green>255?255:green;
        blue=blue<0?0:blue>255?255:blue;

        return (red<<16) | (green<<8) | blue;
      }

}

3
Çok hoş. Kesinlikle şu ana kadarki cevaplardan farklı bir etki veriyor.
Geobits

@Geobits Evet, ne kadar etkili olduğu beni şaşırttı. Bununla birlikte, görsel olarak farklı çıktılar ürettiği için onu bir dithering olarak adlandırıp
tanımayacağımı bilmiyorum

Bu oldukça benzersiz görünüyor.
qwr

5

piton

Fikir şu: Görüntü n x nfayanslara bölünüyor . Bu döşemelerin her birinin ortalama rengini hesaplıyoruz. Ardından renk aralığını bize yeni bir değer veren 0 - 255aralıkla 0 - n*neşleriz v. Sonra o döşemedeki tüm pikselleri siyah ve rastgele renkli vdöşemeleri o döşemenin beyazına renklendiririz . Optimum olmaktan uzak, ancak yine de bize tanınabilir sonuçlar veriyor. Çözünürlük bağlı olarak, genellikle n=2veya en iyi şekilde çalışır n=3. İken n=2size zaten örnekte, 'simüle renk derinliği ait eserleri bulabilirsiniz n=3zaten biraz bulanık alabilirsiniz. Görüntülerin aynı boyutta kalması gerektiğini düşündüm, ancak elbette bu yöntemi kullanabilir ve daha fazla ayrıntı almak için oluşturulan görüntünün boyutunu iki / üç katına çıkarabilirsiniz.

PS: Partiye biraz geç kaldığımı biliyorum, meydan okuma başladığında hiçbir fikrim olmadığını ancak şimdi bu beyin dalgasını yaşadığımı hatırlıyorum =)

from PIL import Image
import random
im = Image.open("dog.jpg") #Can be many different formats.
new = im.copy()
pix = im.load()
newpix = new.load()
width,height=im.size
print([width,height])
print(pix[1,1])

window = 3 # input parameter 'n'

area = window*window
for i in range(width//window):     #loop over pixels
    for j in range(height//window):#loop over pixels
        avg = 0
        area_pix = []
        for k in range(window):
            for l in range(window):
                area_pix.append((k,l))#make a list of coordinates within the tile
                try:
                    avg += pix[window*i+k,window*j+l][0] 
                    newpix[window*i+k,window*j+l] = (0,0,0) #set everything to black
                except IndexError:
                    avg += 255/2 #just an arbitrary mean value (when were outside of the image)
                                # this is just a dirty trick for coping with images that have
                                # sides that are not multiples of window
        avg = avg/area
        # val = v is the number of pixels within the tile that will be turned white
        val = round(avg/255 * (area+0.99) - 0.5)#0.99 due to rounding errors
        assert val<=area,'something went wrong with the val'
        print(val)
        random.shuffle(area_pix) #randomize pixel coordinates
        for m in range(val):
            rel_coords = area_pix.pop()#find random pixel within tile and turn it white
            newpix[window*i+rel_coords[0],window*j+rel_coords[1]] = (255,255,255)

new.save('dog_dithered'+str(window)+'.jpg')

Sonuçlar:

n=2:

görüntü tanımını buraya girin

n=3:

görüntü tanımını buraya girin


3

İstediğiniz herhangi bir dosya formatı iyi.

Mevcut dosya formatlarından herhangi birinin hızlı cevap yazmak için fazla yükü olduğu için bu soru için çok kompakt, teorik bir dosya formatı tanımlayalım.

Görüntü dosyasının ilk dört baytının görüntünün genişliğini ve yüksekliğini sırasıyla piksel cinsinden tanımlamasını sağlayın:

00000001 00101100 00000001 00101100
width: 300 px     height: 300 px

ardından w * h0 ile 255 arasında gri tonlamalı değerlerin baytı:

10000101 10100101 10111110 11000110 ... [89,996 more bytes]

Daha sonra Python'da (145 byte) bu görüntüyü alacak ve yapacak bir kod parçası tanımlayabiliriz:

import random
f=open("a","rb");g=open("b","wb");g.write(f.read(4))
for i in f.read():g.write('\xff' if ord(i)>random.randint(0,255) else '\x00')

ki bu, pikselin gri tonlama değerine eşit olasılıkla beyaz veya siyah döndürerek "öter".


Örnek görüntüye uygulandığında şöyle bir şey verir:

dithered köpek

Çok hoş değil, ancak önizlemede küçültüldüğünde çok benzer gözüküyor ve sadece 145 byte Python için daha iyi olacağınızı sanmıyorum.


Bir örneği paylaşabilir misiniz? Bunun rastgele titremesi olduğuna inanıyorum ve sonuçlar en temiz değil ... güzel profil resmi olsa da
qwr

Bu gerçekten rastgele bir titreme ve şu anda örnek resminize bir örnek veriyorum.
Joe Z.

2
Bunun kontrast artışından fayda sağlayabileceğini düşünüyorum. Python'u tanımıyorum ama rastgele olduğumu farz ediyorum. (0,255), 0 ile 255 arasında rasgele bir sayı seçiyor. 55 ve 200 arasında sınırlamayı deneyin; bu aralığın dışındaki gölgeleri saf siyah veya beyaz olmaya zorlar. Birçok resim ile, titizlik göstermeyen, sadece basit bir eşikli, iyi ve çarpıcı bir görüntü elde edebilirsiniz. (Rastgele + kontrast artışı, geçerli görüntünüz ve basit eşik arasında bir görüntüyü ortaya koyar.)
Level River St

Bence rastgele titreme Geiger titremesi olarak adlandırılmalıdır (çünkü bir Geiger sayacının çıktısına benziyor). Kim katılıyor?
Joe Z.

1
Bu, ImageMagick ve GraphicsMagick'in yıllar önce “sıralı dither” ile birlikte eklediğim “-random-eşiği” seçeneğiyle yaptığı tam olarak budur (cevabımı ekledi). Yine, gama çarpmak, doğru yoğunluğu elde etmenize yardımcı olur. "Geiger taklidi" önerisine katılıyorum.
Glenn Randers-Pehrson

3

kobra

24 bit veya 32 bit PNG / BMP dosyası alır (JPG içindeki bazı gri çıktıları üretir). Ayrıca renk içeren dosyalara genişletilebilir.

Görüntüyü 3 bit renge bölmek için hız optimizasyonlu ELA kullanır ve test resminiz verildiğinde siyah / beyaz olarak geri döner.

Gerçekten hızlı olduğunu söylemiş miydim?

use System.Drawing
use System.Drawing.Imaging
use System.Runtime.InteropServices
use System.IO

class BW_Dither

    var path as String = Directory.getCurrentDirectory to !
    var rng = Random()

    def main
        file as String = Console.readLine to !
        image as Bitmap = Bitmap(.path+"\\"+file)
        image = .dither(image)
        image.save(.path+"\\test\\image.png")

    def dither(image as Bitmap) as Bitmap

        output as Bitmap = Bitmap(image)

        layers as Bitmap[] = @[ Bitmap(image), Bitmap(image), Bitmap(image),
                                Bitmap(image), Bitmap(image), Bitmap(image),
                                Bitmap(image)]

        layers[0].rotateFlip(RotateFlipType.RotateNoneFlipX)
        layers[1].rotateFlip(RotateFlipType.RotateNoneFlipY)
        layers[2].rotateFlip(RotateFlipType.Rotate90FlipX)
        layers[3].rotateFlip(RotateFlipType.Rotate90FlipY)
        layers[4].rotateFlip(RotateFlipType.Rotate90FlipNone)
        layers[5].rotateFlip(RotateFlipType.Rotate180FlipNone)
        layers[6].rotateFlip(RotateFlipType.Rotate270FlipNone)

        for i in layers.length, layers[i] = .dither_ela(layers[i])

        layers[0].rotateFlip(RotateFlipType.RotateNoneFlipX)
        layers[1].rotateFlip(RotateFlipType.RotateNoneFlipY)
        layers[2].rotateFlip(RotateFlipType.Rotate270FlipY)
        layers[3].rotateFlip(RotateFlipType.Rotate270FlipX)
        layers[4].rotateFlip(RotateFlipType.Rotate270FlipNone)
        layers[5].rotateFlip(RotateFlipType.Rotate180FlipNone)
        layers[6].rotateFlip(RotateFlipType.Rotate90FlipNone)

        vals = List<of List<of uint8[]>>()
        data as List<of uint8[]> = .getData(output)
        for l in layers, vals.add(.getData(l))
        for i in data.count, for c in 3
            x as int = 0
            for n in vals.count, if vals[n][i][c] == 255, x += 1
            if x > 3.5, data[i][c] = 255 to uint8
            if x < 3.5, data[i][c] = 0 to uint8

        .setData(output, data)
        return output

    def dither_ela(image as Bitmap) as Bitmap

        error as decimal[] = @[0d, 0d, 0d]
        rectangle as Rectangle = Rectangle(0, 0, image.width, image.height)
        image_data as BitmapData = image.lockBits(rectangle, ImageLockMode.ReadWrite, image.pixelFormat) to !
        pointer as IntPtr = image_data.scan0
        bytes as uint8[] = uint8[](image_data.stride * image.height)
        pfs as int = Image.getPixelFormatSize(image.pixelFormat) // 8
        Marshal.copy(pointer, bytes, 0, image_data.stride * image.height)

        for y as int in image.height, for x as int in image.width
            position as int = (y * image_data.stride) + (x * pfs)
            for i in 3
                error[i] -= bytes[position + i]
                if Math.abs(error[i] + 255 - bytes[position + i]) < Math.abs(error[i] - bytes[position + i])
                    bytes[position + i] = 255 to uint8
                    error[i] += 255
                else, bytes[position + i] = 0 to uint8

        Marshal.copy(bytes, 0, pointer, image_data.stride * image.height)
        image.unlockBits(image_data)
        return image

    def getData(image as Bitmap) as List<of uint8[]>

        rectangle as Rectangle = Rectangle(0, 0, image.width, image.height)
        image_data as BitmapData = image.lockBits(rectangle, ImageLockMode.ReadOnly, image.pixelFormat) to !
        pointer as IntPtr = image_data.scan0
        bytes as uint8[] = uint8[](image_data.stride * image.height)
        pixels as List<of uint8[]> = List<of uint8[]>()
        for i in image.width * image.height, pixels.add(nil)
        pfs as int = Image.getPixelFormatSize(image.pixelFormat) // 8
        Marshal.copy(pointer, bytes, 0, image_data.stride * image.height)

        count as int = 0
        for y as int in image.height, for x as int in image.width
            position as int = (y * image_data.stride) + (x * pfs)
            if pfs == 4, alpha as uint8 = bytes[position + 3]
            else, alpha as uint8 = 255
            pixels[count] = @[
                                bytes[position + 2], #red
                                bytes[position + 1], #green
                                bytes[position],     #blue
                                alpha]               #alpha
            count += 1

        image.unlockBits(image_data)
        return pixels

    def setData(image as Bitmap, pixels as List<of uint8[]>)
        if pixels.count <> image.width * image.height, throw Exception()
        rectangle as Rectangle = Rectangle(0, 0, image.width, image.height)
        image_data as BitmapData = image.lockBits(rectangle, ImageLockMode.ReadWrite, image.pixelFormat) to !
        pointer as IntPtr = image_data.scan0
        bytes as uint8[] = uint8[](image_data.stride * image.height)
        pfs as int = Image.getPixelFormatSize(image.pixelFormat) // 8
        Marshal.copy(pointer, bytes, 0, image_data.stride * image.height)

        count as int = 0
        for y as int in image.height, for x as int in image.width
            position as int = (y * image_data.stride) + (x * pfs)
            if pfs == 4, bytes[position + 3] = pixels[count][3] #alpha
            bytes[position + 2] = pixels[count][0]              #red
            bytes[position + 1] = pixels[count][1]              #green
            bytes[position] = pixels[count][2]                  #blue
            count += 1

        Marshal.copy(bytes, 0, pointer, image_data.stride * image.height)
        image.unlockBits(image_data)

Köpek

Ağaçlar


Tekrarı azaltmak için geçici bir değişken oluşturmayı colve image.setPixel(x,y,col)en sonuna kadar bırakmayı düşündünüz mü?
joeytwiddle

3
Ağaç görüntülerinde ne var?
AJMansfield

Güzel görünüyor ve bu renklerle çalışmanın bir örneğini sunuyor.
Büyük

2

Java

PNGJ ve gürültü ilavesi ile temel difüzyon kullanarak düşük seviye kod . Bu uygulama gri tonlamalı 8 bit PNG kaynağı gerektirir.

import java.io.File;
import java.util.Random;

import ar.com.hjg.pngj.ImageInfo;
import ar.com.hjg.pngj.ImageLineInt;
import ar.com.hjg.pngj.PngReaderInt;
import ar.com.hjg.pngj.PngWriter;

public class Dither {

    private static void dither1(File file1, File file2) {
        PngReaderInt reader = new PngReaderInt(file1);
        ImageInfo info = reader.imgInfo;
        if( info.bitDepth != 8 && !info.greyscale ) throw new RuntimeException("only 8bits grayscale valid");
        PngWriter writer = new PngWriter(file2, reader.imgInfo);
        ImageLineInt line2 = new ImageLineInt(info);
        int[] y = line2.getScanline();
        int[] ye = new int[info.cols];
        int ex = 0;
        Random rand = new Random();
        while(reader.hasMoreRows()) {
            int[] x = reader.readRowInt().getScanline();
            for( int i = 0; i < info.cols; i++ ) {
                int t = x[i] + ex + ye[i];
                y[i] = t > rand.nextInt(256) ? 255 : 0;
                ex = (t - y[i]) / 2;
                ye[i] = ex / 2;
            }
            writer.writeRow(line2);
        }
        reader.end();
        writer.end();
    }

    public static void main(String[] args) {
        dither1(new File(args[0]), new File(args[1]));
        System.out.println("See output in " + args[1]);
    }

}

( Denemek istiyorsanız bu kavanozu yapı yolunuza ekleyin).

görüntü tanımını buraya girin

Bir bonus olarak: Bu bellek kullanımında son derece etkilidir (sadece üç sıra saklar), bu yüzden büyük görüntüler için kullanılabilir.


Nitpick: "Büyük görüntüler için kullanıldığını" çok önemli olmadığını düşünüyorum (hiç> 8 GB gri tonlamalı bir PNG mi gördünüz mü?), Ancak "örneğin gömülü aygıtlarda kullanılmış" çok daha belirgin bir nokta.
yarı ekstrinsik

Hoşuma gitti ama kenarlarda biraz bulanık görünüyor, sanırım.
BobTheAwesome

1

Java

Sadece basit bir RNG tabanlı algoritma, ayrıca renkli görüntülerle başa çıkmak için bir mantık. Herhangi bir pikseli beyaza ayarlama olasılığı b'dir , aksi takdirde siyaha ayarlar; nerede b o pikselin orijinal parlaklık olduğunu.

import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Random;
import java.util.Scanner;

import javax.imageio.ImageIO;


public class Ditherizer {
    public static void main(String[]a) throws IOException{
        Scanner input=new Scanner(System.in);
        Random rand=new Random();
        BufferedImage img=ImageIO.read(new File(input.nextLine()));
        for(int x=0;x<img.getWidth();x++){
            for(int y=0;y<img.getHeight();y++){
                Color c=new Color(img.getRGB(x, y));
                int r=c.getRed(),g=c.getGreen(),b=c.getBlue();
                double bright=(r==g&&g==b)?r:Math.sqrt(0.299*r*r+0.587*g*g+0.114*b*b);
                img.setRGB(x,y,(rand.nextInt(256)>bright?Color.BLACK:Color.WHITE).getRGB());    
            }
        }
        ImageIO.write(img,"jpg",new File(input.nextLine()));
        input.close();
    }
}

İşte köpek görüntüsü için potansiyel bir sonuç:

görüntü tanımını buraya girin


Neden hiç açıklama katmayan üst yerine kimse bunu okuyacak alt? Bu fikri gerçekten beğendim =)
kusur
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.