Dither a Grayscale Image


23

Immagine in scala di grigi in puro bianco e nero con il tuo algoritmo.

Linee guida: è necessario elaborare un nuovo algoritmo. Non è possibile utilizzare algoritmi preesistenti (es. Floyd-Steinburg) ma è possibile utilizzare la tecnica generale. Il tuo programma deve essere in grado di leggere un'immagine e produrre un'immagine della stessa dimensione. Questo è un concorso di popolarità, quindi chiunque produca le vittorie migliori (più vicine all'originale) e più creative (determinate dai voti). Bonus se il codice è breve, anche se questo non è necessario.

È possibile utilizzare qualsiasi immagine in scala di grigi desiderata come input, dovrebbe essere maggiore di 300x300. Qualsiasi formato di file va bene.

Esempio di input:

cucciolo

Esempio di output:

dithering

Questo è un ottimo lavoro, ma ci sono ancora linee e motivi visibili.


4
+1 per una sfida interessante, ma penso che sarebbe molto meglio come un [code-golf] (con una specifica) o qualche altro criterio completamente oggettivo.
Maniglia della porta

2
Il problema con la dimensione del codice, la velocità e l'utilizzo della memoria è che avresti bisogno di una soglia obiettiva per quanto deve essere riconoscibile il risultato affinché la risposta sia valida, il che è anche abbastanza impossibile. Il concorso di popolarità ha un senso, ma senza alcuna restrizione sul codice non vi è alcun incentivo per le persone a pensare fuori dagli schemi. Preferirei dare una risposta intelligente piuttosto che una che dia il miglior risultato perché ha appena implementato un algoritmo esistente. Ma attualmente stai incentivando quest'ultimo.
Martin Ender,

3
La linea tra un algoritmo e la sua tecnica è troppo sottile per determinare da che parte cade qualcosa.
Peter Taylor,

2
Penso che sarebbe molto più facile confrontare i risultati se tutti mostrassero risultati dalla stessa immagine.
joeytwiddle,

3
Puoi aggiungere la fonte dell'immagine? (Non credo che qualcuno sarà arrabbiato nel vedere la sua immagine qui, ma è giusto citare la fonte)
AL

Risposte:


16

Fortran

Ok, sto usando un oscuro formato di immagine chiamato FITS che viene usato per l'astronomia. Ciò significa che esiste una libreria Fortran per leggere e scrivere tali immagini. Inoltre, ImageMagick e Gimp possono sia leggere / scrivere immagini FITS.

L'algoritmo che utilizzo si basa sul dithering "Sierra Lite", ma con due miglioramenti:
a) Riduco l'errore propagato di un fattore 4/5.
b) presento una variazione casuale nella matrice di diffusione mantenendo costante la sua somma.
Insieme, questi elementi eliminano quasi completamente gli schemi osservati nell'esempio dei PO.

Supponendo di avere installato la libreria CFITSIO, compilare con

gfortran -lcfitsio dither.f90

I nomi dei file sono hardcoded (non potrebbe essere disturbato per risolvere questo problema).

Codice:

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

Esempio di output per l'immagine del cucciolo nel post
Maschera retinata del cucciolo
OP: output di esempio OP:
Gli OP hanno retinato l'immagine del cucciolo


Sembra davvero buono, potrebbe essere imbattibile per qualità
aditsu

Grazie! Non so che sia imbattibile, ma sarà difficile (molto soggettivo) giudicarlo rispetto ad altri buoni algoritmi.
semi-estrinseco

1
So che uso il codice del golf abusando della retrocompatibilità, ma in realtà sembra che tu lo abusi di serie. Questo codice mi sta davvero facendo piangere.
Kyle Kanos,

@KyleKanos Sono sempre felice quando il mio codice fa piangere qualcuno: p In materia, che cosa è specificamente orribile qui? Sì, avrei potuto usare "implicito nessuno", ma dov'è il divertimento? Lo uso per scrivere codice sul lavoro, ma non per giocare a golf. E sono assolutamente d'accordo sul fatto che l'API della libreria CFITSIO sia completamente orribile (ftppre () genera un'immagine FITS in una singola precisione reale, ftpprj () produce un'immagine con precisione a doppio numero intero, ecc.) Ma questa è la compatibilità con F77 all'indietro per te.
semi-estrinseco

1
Ok, quindi molti di questi erano solo io che sono sciatta. L'ho migliorato. Le critiche costruttive sono sempre apprezzate :)
semi-estrinseco

34

GraphicsMagick / ImageMagick

Dither ordinato:

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

Prima di lamentarmi del mio utilizzo di un "algoritmo consolidato", leggi ChangeLog per GraphicsMagick e ImageMagick per aprile 2003, dove vedrai che ho implementato l'algoritmo in quelle applicazioni. Inoltre, la combinazione di "-gamma .45455" con "-ordered-dither" è nuova.

"-Gamma .45455" si prende cura che l'immagine sia troppo chiara. Il parametro "all" è necessario solo con GraphicsMagick.

C'è banda perché ci sono solo 17 graylevel in un'immagine di dithering 4x4 ordinata. L'aspetto del bendaggio può essere ridotto utilizzando un dithering 8x8 con 65 livelli.

Ecco l'immagine originale, l'uscita 4x4 e 8x8 ordinata con dithering e l'uscita a soglia casuale: inserisci qui la descrizione dell'immagine

Preferisco la versione ordinata, ma per completezza includo la versione con soglia casuale.

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

Il "10x90%" significa rendere i pixel con intensità inferiore al 10 percento come nero puro e superiori al 90 percento come bianco puro, per evitare di avere alcuni punti solitari in quelle aree.

Probabilmente vale la pena notare che entrambi sono tanto efficienti quanto la memoria. Né fa alcuna diffusione, quindi funzionano un pixel alla volta, anche quando scrivono i blocchi di dithering ordinati, e non hanno bisogno di sapere nulla dei pixel vicini. ImageMagick e GraphicsMagick elaborano una riga alla volta, ma non è necessario per questi metodi. Le conversioni di dithering ordinate richiedono meno di 0,04 secondi in tempo reale sul mio vecchio computer x86_64.


31
"Prima di lamentarmi del mio utilizzo di un" algoritmo consolidato ", leggi ChangeLog per GraphicsMagick e ImageMagick per aprile 2003, dove vedrai che ho implementato l'algoritmo in quelle applicazioni." +1 per la guancia pura.
Joe Z.

22

Mi scuso per lo stile del codice, l'ho messo insieme usando alcune librerie che abbiamo appena costruito nella mia classe Java e ha un brutto caso di copia-incolla e numeri magici. L'algoritmo seleziona rettangoli casuali nell'immagine e verifica se la luminosità media è maggiore nell'immagine retinata o nell'immagine originale. Quindi attiva o disattiva un pixel per ravvicinare le luminosità, scegliendo preferibilmente pixel più diversi dall'immagine originale. Penso che faccia un lavoro migliore mettendo in evidenza dettagli sottili come i peli del cucciolo, ma l'immagine è più rumorosa perché cerca di mettere in evidenza i dettagli anche in aree senza nessuno.

inserisci qui la descrizione dell'immagine

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

Suppongo che questo sia deterministico? In tal caso, quanto è veloce?
Οuroso

È casuale e impiega circa 3 secondi sul mio computer.
QuadmasterXLII

2
Anche se forse non è l'algoritmo di massima fedeltà, i risultati sono arte da soli.
AJMansfield,

4
Mi piace molto l'aspetto di questo algoritmo! Ma penso che forse sia così bello in parte perché produce una trama più o meno simile alla pelliccia, e questo è un animale con pelliccia. Ma non sono completamente sicuro che questo sia vero. Potresti pubblicare un'altra immagine, ad esempio di un'auto?
semi-estrinseco,

1
Penso che questa sia la risposta migliore, sia in termini di originalità dell'algoritmo sia in termini di bellezza dei risultati. Mi piacerebbe anche vederlo funzionare anche su alcune altre immagini.
Nathaniel,

13

Ghostscript (con poco aiuto di ImageMagick)

Lungi dall'essere il mio "nuovo algoritmo", ma, scusa, non ho potuto resistere.

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 -

inserisci qui la descrizione dell'immagine

Ovviamente funziona meglio senza restrizioni della stessa dimensione.


2
Questo è divertente. Sono sbalordito dal fatto che nessuno abbia commentato questa meraviglia in stile Warhol.
Andreï Kostyrka,

10

GIAVA

Ecco la mia presentazione. Acquisisce un'immagine JPG, calcola la luminosità pixel per pixel (grazie a Bonan in questa domanda SO) e quindi lo confronta con uno schema casuale per sapere se il pixel risultante sarà bianco o nero. I pixel più scuri saranno sempre neri e i pixel più luminosi saranno sempre bianchi per preservare i dettagli dell'immagine.

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

}

Immagine elaborata

Altri esempi:

Originale Processed

Funziona anche con immagini a colori:

Immagine a colori Risultato


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 byte :)
Utilizza il formato ASCII PGM (P2) senza riga di commento, sia per l'input che per l'output.

Il metodo è molto semplice: aggiunge quadrati di 2 * 2 pixel, converte nell'intervallo 0..4, quindi utilizza un modello corrispondente di 4 bit per generare pixel in bianco e nero 2 * 2.
Ciò significa anche che la larghezza e l'altezza devono essere pari.

Campione:

cucciolo deterministico

E un algoritmo casuale in soli 27 byte:

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

Utilizza lo stesso formato di file.

Campione:

cucciolo a caso

E infine un approccio misto: dithering casuale con una propensione verso uno schema a scacchiera; 44 byte:

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

Campione:

cucciolo misto


2
Il primo è paragonabile all'applicazione "Flipnote Studio" di Nintendo DSi.
BobTheAwesome

6

Java (1.4+)

Non sono sicuro se sto reinventando la ruota qui, ma penso che possa essere unica ...

con sequenze casuali limitate

Con sequenze casuali limitate

Dithering casuale puro

Dithering casuale puro

inserisci qui la descrizione dell'immagine

Immagine della città dalla risposta di Averroè

L'algoritmo utilizza il concetto di energia di luminosità localizzata e normalizzazione per conservare le caratteristiche. La versione iniziale ha quindi utilizzato un jitter casuale per produrre uno sguardo retinato su aree con luminosità simile. Tuttavia non era così visivamente attraente. Per contrastare questo, una serie limitata di sequenze casuali limitate viene mappata sulla luminosità dei pixel di input grezzi e i campioni vengono utilizzati in modo iterativo e ripetutamente producendo sfondi dall'aspetto retinato.

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
Molto bella. Sicuramente dà un effetto diverso rispetto alle altre risposte finora.
Geobits il

@Geobits Sì, mi ha sorpreso quanto sia efficace. Tuttavia non sono sicuro se lo definirei un dithering in quanto produce un output visivamente diverso
Moogie

Sembra davvero unico.
qwr

5

Pitone

L'idea è la seguente: L'immagine viene divisa in n x ntessere. Calcoliamo il colore medio di ciascuna di quelle tessere. Quindi mappiamo la gamma di colori 0 - 255alla gamma 0 - n*nche ci dà un nuovo valore v. Quindi coloriamo tutti i pixel di quella tessera in nero e casualmente coloriamo i vpixel in quella tessera in bianco. È tutt'altro che ottimale ma ci dà ancora risultati riconoscibili. A seconda della risoluzione, di solito funziona meglio su n=2o n=3. Mentre in n=2te puoi già trovare artefatti dall'intensità del colore simulata, nel caso in n=3cui possa già diventare un po 'sfocato. Supponevo che le immagini dovessero avere le stesse dimensioni, ma ovviamente puoi anche usare questo metodo e semplicemente raddoppiare / triplicare le dimensioni dell'immagine generata per ottenere maggiori dettagli.

PS: so che sono un po 'in ritardo alla festa, ricordo che non avevo idee quando è iniziata la sfida, ma ora ho avuto questa onda cerebrale =)

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')

risultati:

n=2:

inserisci qui la descrizione dell'immagine

n=3:

inserisci qui la descrizione dell'immagine


3

Qualsiasi formato di file desiderato va bene.

Definiamo un formato di file teorico molto compatto per questa domanda poiché uno qualsiasi dei formati di file esistenti ha un sovraccarico eccessivo per cui scrivere una risposta rapida.

Consenti ai primi quattro byte del file di immagine di definire rispettivamente la larghezza e l'altezza dell'immagine in pixel:

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

seguito da w * hbyte con valori di gradazione di grigio da 0 a 255:

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

Quindi, possiamo definire un pezzo di codice in Python (145 byte) che prenderà questa immagine e farà:

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')

quale "dithers" restituendo bianco o nero con probabilità uguale al valore di gradazione di grigio di quel pixel.


Applicato sull'immagine di esempio, dà qualcosa del genere:

cane retinato

Non è troppo carino, ma sembra molto simile se ridimensionato in un'anteprima e per soli 145 byte di Python, non penso che tu possa migliorare molto.


Puoi condividere un esempio? Credo che questo sia un dithering casuale, e i risultati non sono il più pulito ... bella foto del profilo
qwr

Questo è davvero un dithering casuale e al momento sto facendo un esempio della tua foto di esempio.
Joe Z.,

2
Penso che potrebbe beneficiare di un aumento del contrasto. Non conosco Python, ma presumo random.randint (0,255) sta selezionando un numero casuale compreso tra 0 e 255. Prova a limitare tra diciamo 55 e 200, il che costringerà tutte le sfumature al di fuori di tale intervallo a essere bianco o nero puro. Con molte immagini puoi ottenere un'immagine bella e sorprendente senza alcun dithering, solo una semplice soglia. (L'aumento casuale + contrasto darebbe un'immagine intermedia tra l'immagine attuale e la soglia semplice.)
Level River St

Penso che il dithering casuale dovrebbe essere chiamato dithering Geiger (perché sembra l'output di un contatore Geiger). Chi è d'accordo?
Joe Z.

1
Questo è quasi esattamente ciò che fanno ImageMagick e GraphicsMagick con l'opzione "-random -reshold" che ho aggiunto insieme a "-ordered-dither" anni fa (aggiunto alla mia risposta). Ancora una volta, il potenziamento della gamma aiuta a ottenere la giusta intensità. Sono d'accordo con il suggerimento "Geiger dithering".
Glenn Randers-Pehrson,

3

Cobra

Acquisisce un file PNG / BMP a 24 o 32 bit (JPG produce output con alcuni grigi). È inoltre estendibile ai file contenenti colore.

Utilizza ELA ottimizzato per la velocità per reticolare l'immagine in colore a 3 bit, che tornerà come bianco / nero quando viene fornita l'immagine di prova.

Ho già detto che è davvero veloce?

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)

Cane

Alberi


Per ridurre la ripetizione, hai preso in considerazione la creazione di una variabile temporanea cole image.setPixel(x,y,col)la fine fino alla fine?
joeytwiddle,

3
Cos'è l'immagine degli alberi?
AJMansfield,

Sembra carino e fornisce un esempio di questo lavoro anche con i colori.
Οuroso

2

Giava

Codice di basso livello, usando PNGJ e un'aggiunta di rumore più diffusione di base. Questa implementazione richiede una sorgente PNG a 8 bit in scala di grigi.

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

}

(Aggiungi questo vaso al tuo percorso di compilazione se vuoi provarlo).

inserisci qui la descrizione dell'immagine

Come bonus: questo è estremamente efficiente nell'uso della memoria (memorizza solo tre file) in modo da poter essere utilizzato per immagini di grandi dimensioni.


Nitpick: Penso che "usato per immagini enormi" non sia così importante (hai mai visto un PNG in scala di grigi> 8 GB?), Ma "utilizzato su dispositivi integrati ad esempio" è un punto molto più saliente.
semi-estrinseco

Mi piace ma sembra un po 'sfocato attorno ai bordi, penso.
BobTheAwesome

1

Giava

Solo un semplice algoritmo basato su RNG, oltre ad alcune logiche per gestire le immagini a colori. Ha la probabilità b di impostare un dato pixel su bianco, altrimenti lo imposta su nero; dove b è la luminosità originale di quel pixel.

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

Ecco un potenziale risultato per l'immagine del cane:

inserisci qui la descrizione dell'immagine


Perché non aggiungi la spiegazione in alto invece che in fondo dove nessuno la leggerà? Mi piace davvero quell'idea =)
flawr
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.