Dithering eines Graustufenbilds


23

Verwandeln Sie ein Graustufenbild mit Ihrem eigenen Algorithmus in reines Schwarzweiß.

Richtlinien: Sie müssen sich einen eigenen neuen Algorithmus ausdenken. Sie können keine vorhandenen Algorithmen (z. B. Floyd-Steinburg) verwenden, aber Sie können die allgemeine Technik verwenden. Ihr Programm muss in der Lage sein, ein Bild zu lesen und ein Bild derselben Größe zu erstellen. Dies ist ein Beliebtheitswettbewerb, bei dem jeder gewinnt, der die besten (dem Original am nächsten kommenden) und kreativsten (durch Stimmen bestimmten) Ergebnisse erzielt. Bonus, wenn der Code kurz ist, obwohl dies nicht erforderlich ist.

Sie können jedes gewünschte Graustufenbild als Eingabe verwenden. Es sollte größer als 300 x 300 sein. Jedes Dateiformat ist in Ordnung.

Beispiel Eingabe:

Hündchen

Beispielausgabe:

zitterte

Das ist ein ziemlich guter Job, aber es gibt immer noch sichtbare Linien und Muster.


4
+1 für eine interessante Herausforderung, aber ich denke, dies wäre viel besser als [Code-Golf] (mit einer Spezifikation) oder ein anderes völlig objektives Kriterium.
Türklinke

2
Das Problem mit der Codegröße, der Geschwindigkeit und der Speichernutzung besteht darin, dass Sie einen objektiven Schwellenwert für die Erkennbarkeit des Ergebnisses benötigen, damit die Antwort gültig ist. Dies ist auch völlig unmöglich. Beliebtheitswettbewerbe sind zwar sinnvoll, aber ohne Einschränkung des Codes gibt es keinen Anreiz für Leute, über den Tellerrand hinauszudenken. Ich würde lieber eine clevere Antwort positiv bewerten als eine, die das beste Ergebnis liefert, weil sie nur einen vorhandenen Algorithmus implementiert. Aber Sie setzen derzeit Anreize für Letzteres.
Martin Ender

3
Die Grenze zwischen einem Algorithmus und seiner Technik ist zu dünn, um zu bestimmen, auf welche Seite etwas fällt.
Peter Taylor

2
Ich denke, es wäre viel einfacher, die Ergebnisse zu vergleichen, wenn sie alle vom selben Bild stammen würden.
Joeytwiddle

3
Können Sie die Quelle des Bildes hinzufügen? (Ich glaube nicht, dass jemand wütend sein wird, sein / ihr Bild hier zu sehen, aber es ist fair, die Quelle zu zitieren)
AL

Antworten:


16

Fortran

Okay, ich verwende ein obskures Bildformat namens FITS, das für die Astronomie verwendet wird. Dies bedeutet, dass es eine Fortran-Bibliothek zum Lesen und Schreiben solcher Bilder gibt. ImageMagick und Gimp können auch FITS-Bilder lesen / schreiben.

Der von mir verwendete Algorithmus basiert auf "Sierra Lite" -Dithering, jedoch mit zwei Verbesserungen:
a) Ich reduziere den propagierten Fehler um einen Faktor 4/5.
b) Ich füge eine zufällige Variation in die Diffusionsmatrix ein und halte dabei ihre Summe konstant.
Zusammen beseitigen diese fast vollständig die Muster, die in dem Beispiel von OPs zu sehen sind.

Wenn Sie die CFITSIO-Bibliothek installiert haben, kompilieren Sie mit

gfortran -lcfitsio dither.f90

Die Dateinamen sind fest codiert (wir konnten uns nicht die Mühe machen, dies zu beheben).

Code:

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

Beispielausgabe für das Welpenbild in OPs post:
Dithering Bild des Welpen
OPs Beispielausgabe:
OPs zitterten Bild des Welpen


Das sieht wirklich gut aus, könnte für die Qualität unschlagbar sein
aditsu

Vielen Dank! Ich weiß nicht, dass es unschlagbar ist, aber es wird schwierig (sehr subjektiv) sein, dies an anderen guten Algorithmen zu messen.
Semi-Extrinsic

1
Ich weiß, dass ich Code durch Missbrauch der Abwärtskompatibilität Golf spiele, aber es sieht tatsächlich so aus, als ob Sie ihn als Standard missbrauchen. Dieser Code bringt mich zum Weinen.
Kyle Kanos

@KyleKanos Ich bin immer froh, wenn mein Code jemanden zum Weinen bringt: p Zum Thema, was genau ist hier schrecklich? Ja, ich hätte "implicit none" verwenden können, aber wo ist der Spaß dabei? Ich benutze es für ernstes Programmieren bei der Arbeit, aber nicht zum Golfen. Und ich stimme definitiv zu, dass die CFITSIO-Bibliotheks-API völlig schrecklich ist (ftppre () gibt ein FITS-Bild mit einfacher reeller Genauigkeit aus, ftpprj () gibt ein Bild mit doppelter ganzzahliger Genauigkeit aus, usw.), aber das ist für Sie die F77-Abwärtskompatibilität.
Semi-Extrinsic

1
Okay, die meisten davon waren nur, dass ich schlampig war. Ich habe es verbessert. Konstruktive Kritik wird immer geschätzt :)
semi-extrinsic

34

GraphicsMagick / ImageMagick

Bestellte Dither:

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

Bevor Sie sich über die Verwendung eines "etablierten Algorithmus" beschweren, lesen Sie bitte das ChangeLog für GraphicsMagick und ImageMagick für April 2003, in dem Sie sehen, dass ich den Algorithmus in diesen Anwendungen implementiert habe. Neu ist auch die Kombination von "-gamma .45455" mit "-ordered-dither".

Das "-gamma .45455" sorgt dafür, dass das Bild zu hell wird. Der Parameter "all" wird nur bei GraphicsMagick benötigt.

Es kommt zu Streifenbildung, da ein 4x4-Bild mit geordnetem Dither nur 17 Graustufen enthält. Das Auftreten von Streifenbildung kann durch Verwendung eines 8x8-Ordered-Dithering mit 65 Stufen reduziert werden.

Hier sind das Originalbild, die geordnete 4 x 4 und 8 x 8-Dither-Ausgabe und die Zufallsschwellenausgabe: Bildbeschreibung hier eingeben

Ich bevorzuge die Ordered-Dither-Version, der Vollständigkeit halber jedoch auch die Random-Threshold-Version.

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

"10x90%" bedeutet, Pixel mit einer Intensität von unter 10 Prozent als reines Schwarz und über 90 Prozent als reines Weiß wiederzugeben, um zu vermeiden, dass in diesen Bereichen einige einsame Flecken auftreten.

Es ist wahrscheinlich erwähnenswert, dass beide so speichereffizient sind, wie sie nur sein können. Es gibt auch keine Diffusion, daher arbeiten sie pixelweise, auch wenn sie geordnete Dither-Blöcke schreiben, und müssen nichts über die benachbarten Pixel wissen. ImageMagick und GraphicsMagick verarbeiten jeweils eine Zeile, dies ist jedoch für diese Methoden nicht erforderlich. Die geordneten Dither-Konvertierungen dauern auf meinem alten x86_64-Computer weniger als 0,04 Sekunden in Echtzeit.


31
"Bevor Sie sich über die Verwendung eines" etablierten Algorithmus "beschweren, lesen Sie bitte das ChangeLog für GraphicsMagick und ImageMagick für April 2003, in dem Sie sehen, dass ich den Algorithmus in diesen Anwendungen implementiert habe." +1 für reine Wange.
Joe Z.

22

Ich entschuldige mich für den Codestil, den ich mithilfe einiger Bibliotheken zusammengefügt habe, die wir gerade in meiner Java-Klasse erstellt haben, und es gibt einen schlimmen Fall von Copy-Paste und magischen Zahlen. Der Algorithmus wählt zufällige Rechtecke im Bild aus und überprüft, ob die durchschnittliche Helligkeit im geditherten Bild oder im Originalbild größer ist. Anschließend wird ein Pixel ein- oder ausgeschaltet, um die Helligkeit näher an die Linie heranzuführen. Dabei werden vorzugsweise Pixel ausgewählt, die sich stärker vom Originalbild unterscheiden. Ich denke, es macht einen besseren Job, wenn dünne Details wie die Haare des Welpen hervorgehoben werden, aber das Bild ist lauter, weil es versucht, Details auch in Bereichen hervorzuheben, in denen keine vorhanden sind.

Bildbeschreibung hier eingeben

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

Ich nehme an, dass dies deterministisch ist? Wenn ja, wie schnell ist es?
Οurous

Es ist zufällig und dauert ungefähr 3 Sekunden auf meinem Computer.
QuadmasterXLII

2
Obwohl dies möglicherweise nicht der Algorithmus mit der höchsten Wiedergabetreue ist, sind die Ergebnisse Kunst für sich.
AJMansfield

4
Ich mag das Aussehen dieses Algorithmus! Aber ich denke, vielleicht sieht es auch deshalb so gut aus, weil es eine Textur erzeugt, die in etwa dem Fell ähnelt, und dies ist ein Tier mit Fell. Ich bin mir aber nicht ganz sicher, ob das stimmt. Könnten Sie ein anderes Bild von zB einem Auto posten?
Semi-Extrinsic

1
Ich denke, dies ist die beste Antwort, sowohl in Bezug auf die Originalität des Algorithmus als auch in Bezug auf die beeindruckenden Ergebnisse. Ich würde es auch sehr gerne auf einigen anderen Bildern sehen.
Nathaniel

13

Ghostscript (mit etwas Hilfe von ImageMagick)

Weit davon entfernt, mein "eigener neuer Algorithmus" zu sein, aber ich konnte ihm leider nicht widerstehen.

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 -

Bildbeschreibung hier eingeben

Natürlich funktioniert es besser, ohne die gleiche Größe zu haben.


2
Das ist urkomisch. Ich bin fassungslos darüber, dass sich niemand zu diesem Wunder im Warhol-Stil geäußert hat.
Andreï Kostyrka

10

JAVA

Hier ist meine Vorlage. Nimmt ein JPG-Bild auf, berechnet die Helligkeit pro Pixel (dank Bonan in dieser SO-Frage) und vergleicht sie mit einem zufälligen Muster, um festzustellen, ob das resultierende Pixel schwarz oder weiß ist. Die dunkelsten Pixel sind immer schwarz und die hellsten Pixel sind immer weiß, um Bilddetails beizubehalten.

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

}

Verarbeitetes Bild

Andere Beispiele:

Original Verarbeitet

Funktioniert auch mit Farbbildern:

Farbbild Ergebnis


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 :)
Es wird das ASCII-PGM-Format (P2) ohne Kommentarzeile sowohl für die Eingabe als auch für die Ausgabe verwendet.

Die Methode ist sehr einfach: Sie addiert Quadrate von 2 * 2 Pixeln, konvertiert in den Bereich 0..4 und verwendet dann ein entsprechendes Muster von 4 Bits, um 2 * 2 Schwarzweißpixel zu erzeugen.
Das bedeutet auch, dass Breite und Höhe gleichmäßig sein müssen.

Probe:

deterministischer Welpe

Und ein Zufallsalgorithmus in nur 27 Bytes:

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

Es wird das gleiche Dateiformat verwendet.

Probe:

zufälliger Welpe

Und schließlich ein gemischter Ansatz: zufälliges Dithering mit einer Tendenz zu einem Schachbrettmuster; 44 Bytes:

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

Probe:

gemischter Welpe


2
Die erste ist vergleichbar mit der "Flipnote Studio" -Anwendung des Nintendo DSi.
BobTheAwesome

6

Java (1.4+)

Ich bin mir nicht sicher, ob ich das Rad hier neu erfinden werde, aber ich denke, es könnte einzigartig sein ...

mit begrenzten zufälligen Sequenzen

Mit begrenzten Zufallsfolgen

Reines zufälliges Dithering

Reines zufälliges Dithering

Bildbeschreibung hier eingeben

Stadtbild aus Averroes 'Antwort

Der Algorithmus verwendet das Konzept der lokalisierten Helligkeitsenergie und der Normalisierung, um Merkmale beizubehalten. Die ursprüngliche Version verwendete dann einen zufälligen Jitter, um einen geditherten Blick über Bereiche mit ähnlicher Leuchtkraft zu erzeugen. Es war jedoch optisch nicht so ansprechend. Um dem entgegenzuwirken, wird ein begrenzter Satz von begrenzten Zufallssequenzen auf die Helligkeit der rohen Eingangspixel abgebildet, und es werden iterativ und wiederholt Abtastwerte verwendet, um einen zitternd wirkenden Hintergrund zu erhalten.

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
Sehr schön. Es hat definitiv einen anderen Effekt als die bisherigen Antworten.
Geobits

@ Geobits Ja, es hat mich überrascht, wie effektiv es ist. Ich bin mir jedoch nicht sicher, ob ich es ein Dithering nennen würde, da es eine ganz andere visuelle Ausgabe erzeugt
Moogie

Das sieht wirklich einzigartig aus.
Qwr

5

Python

Die Idee ist folgende: Das Bild wird in n x nKacheln aufgeteilt. Wir berechnen die durchschnittliche Farbe jeder dieser Kacheln. Dann ordnen wir den Farbbereich dem Bereich 0 - 255zu, der 0 - n*nuns einen neuen Wert gibt v. Dann färben wir alle Pixel dieser Kachel schwarz und die vPixel in dieser Kachel weiß nach dem Zufallsprinzip . Es ist alles andere als optimal, liefert aber dennoch erkennbare Ergebnisse. Abhängig von der Auflösung funktioniert es normalerweise am besten mit n=2oder n=3. Während inn=2 Sie sich , können Sie bereits Artefakte aus der simulierten Farbtiefe finden, für den Fall, dass n=3es bereits etwas unscharf werden kann. Ich ging davon aus, dass die Bilder die gleiche Größe haben sollten, aber Sie können natürlich auch diese Methode verwenden und die Größe des generierten Bildes verdoppeln / verdreifachen, um mehr Details zu erhalten.

PS: Ich weiß, dass ich ein bisschen zu spät zur Party komme. Ich erinnere mich, dass ich zu Beginn der Herausforderung keine Ideen hatte, aber jetzt nur diese Gehirnwelle hatte =)

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

Ergebnisse:

n=2:

Bildbeschreibung hier eingeben

n=3:

Bildbeschreibung hier eingeben


3

Jedes gewünschte Dateiformat ist in Ordnung.

Definieren wir ein sehr kompaktes, theoretisches Dateiformat für diese Frage, da jedes der vorhandenen Dateiformate zu viel Overhead hat, um eine schnelle Antwort darauf zu schreiben.

Lassen Sie die ersten vier Bytes der Bilddatei die Breite und Höhe des Bildes in Pixel definieren:

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

gefolgt von w * hGraustufen-Bytes von 0 bis 255:

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

Dann können wir ein Stück Code in Python (145 Bytes) definieren, das dieses Bild aufnimmt und Folgendes ausführt:

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

die "dithert", indem sie Weiß oder Schwarz mit einer Wahrscheinlichkeit zurückgibt, die gleich dem Graustufenwert dieses Pixels ist.


Auf das Beispielbild angewendet, ergibt sich so etwas:

zappelnder Hund

Es ist nicht besonders hübsch, sieht aber in der Vorschau sehr ähnlich aus, und für nur 145 Python-Bytes glaube ich nicht, dass Sie viel besser werden können.


Können Sie ein Beispiel teilen? Ich glaube, das ist zufälliges Dithering, und die Ergebnisse sind nicht die saubersten ... nettes Profilbild allerdings
qwr

Dies ist in der Tat zufälliges Dithering, und ich mache im Moment ein Beispiel für Ihr Beispielbild.
Joe Z.

2
Ich denke, es könnte von einem Kontrastschub profitieren. Ich kenne Python nicht, aber ich gehe davon aus, dass random.randint (0,255) eine Zufallszahl zwischen 0 und 255 auswählt. Versuchen Sie, sie auf etwa 55 bis 200 zu beschränken, damit alle Schattierungen außerhalb dieses Bereichs rein schwarz oder weiß sind. Mit vielen Bildern erhalten Sie ein gutes, beeindruckendes Bild ohne Dithering, nur eine einfache Schwelle. (Random + Contrast Boost würde ein Bild zwischen Ihrem aktuellen Bild und der einfachen Schwelle ergeben.)
Level River St

Ich denke, zufälliges Dithering sollte als Geiger-Dithering bezeichnet werden (da es wie die Ausgabe eines Geigerzählers aussieht). Wer stimmt dem zu?
Joe Z.

1
Genau das tun ImageMagick und GraphicsMagick mit der Option "-random-threshold", die ich vor Jahren zusammen mit "-ordered-dither" hinzugefügt habe (meiner Antwort hinzugefügt). Auch hier hilft das Erhöhen des Gammas dabei, die richtige Intensität zu erzielen. Ich stimme dem Vorschlag "Geiger-Dithering" zu.
Glenn Randers-Pehrson

3

Kobra

Nimmt eine 24-Bit- oder 32-Bit-PNG / BMP-Datei auf (JPG erzeugt eine Ausgabe mit einigen Grautönen). Es ist auch erweiterbar auf Dateien, die Farbe enthalten.

Es verwendet eine geschwindigkeitsoptimierte ELA, um das Bild in 3-Bit-Farben zu zittern, die bei Eingabe des Testbilds als Schwarz / Weiß ausgegeben werden.

Habe ich schon erwähnt, dass es wirklich schnell ist?

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)

Hund

Bäume


Haben Sie darüber nachgedacht, eine temporäre Variable zu erstellen colund die Variable image.setPixel(x,y,col)bis zum Ende zu belassen, um Wiederholungen zu vermeiden ?
Joeytwiddle

3
Was ist mit dem Baumbild?
AJMansfield

Es sieht gut aus und bietet ein Beispiel dafür, wie man auch mit Farben arbeitet.
Οurous

2

Java

Low-Level-Code mit PNGJ und Rauschaddition plus Basisdiffusion . Diese Implementierung erfordert eine Graustufen-8-Bit-PNG-Quelle.

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

}

(Fügen Sie dieses Glas Ihrem Erstellungspfad hinzu, wenn Sie es ausprobieren möchten).

Bildbeschreibung hier eingeben

Als Bonus: Dies ist äußerst effizient bei der Speichernutzung (es werden nur drei Zeilen gespeichert), sodass es für große Bilder verwendet werden kann.


Nitpick: Ich denke, "für große Bilder verwendet" ist nicht so wichtig (haben Sie jemals ein> 8 GB Graustufen-PNG gesehen?), Aber "für eingebettete Geräte verwendet" ist ein viel wichtigerer Punkt.
Semi-Extrinsic

Ich mag es, aber es sieht ein bisschen verschwommen an den Rändern aus, denkt nach.
BobTheAwesome

1

Java

Nur ein einfacher RNG-basierter Algorithmus sowie eine Logik für den Umgang mit Farbbildern. Hat die Wahrscheinlichkeit, dass b ein bestimmtes Pixel auf Weiß setzt, setzt es andernfalls auf Schwarz; Dabei ist b die ursprüngliche Helligkeit dieses Pixels.

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

Hier ist ein mögliches Ergebnis für das Hundebild:

Bildbeschreibung hier eingeben


Warum fügst du die Erklärung nicht oben statt unten hinzu, wo niemand sie lesen wird? Ich mag diese Idee wirklich =)
Fehler
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.