Dither une image en niveaux de gris


23

Dither une image en niveaux de gris en noir et blanc pur avec votre propre algorithme.

Lignes directrices: Vous devez créer votre propre nouvel algorithme. Vous ne pouvez pas utiliser d'algorithmes préexistants (ex. Floyd-Steinburg) mais vous pouvez utiliser la technique générale. Votre programme doit pouvoir lire une image et produire une image de la même taille. Il s'agit d'un concours de popularité, donc celui qui produit le meilleur (le plus proche de l'original) et le plus créatif (déterminé par les votes) gagne. Bonus si le code est court, mais ce n'est pas nécessaire.

Vous pouvez utiliser n'importe quelle image en niveaux de gris que vous souhaitez comme entrée, elle doit être supérieure à 300x300. Tout format de fichier est correct.

Exemple d'entrée:

chiot

Exemple de sortie:

tergiversé

C'est un très bon travail, mais il reste des lignes et des motifs visibles.


4
+1 pour un défi intéressant, mais je pense que ce serait bien mieux comme un [code-golf] (avec une spécification) ou un autre critère complètement objectif.
Poignée de porte

2
Le problème avec la taille du code, la vitesse et l'utilisation de la mémoire est que vous auriez besoin d'un seuil objectif pour la reconnaissance du résultat pour que la réponse soit valide, ce qui est également tout à fait impossible. Le concours de popularité a du sens, mais sans aucune restriction sur le code, les gens ne sont pas incités à sortir des sentiers battus. Je préfère voter pour une réponse intelligente que celle qui donne le meilleur résultat car elle vient de mettre en œuvre un algorithme existant. Mais vous incitez actuellement ce dernier.
Martin Ender

3
La ligne entre un algorithme et sa technique est trop mince pour déterminer de quel côté quelque chose tombe.
Peter Taylor

2
Je pense qu'il serait beaucoup plus facile de comparer les résultats s'ils montraient tous des résultats provenant de la même image.
joeytwiddle

3
Pouvez-vous ajouter la source de l'image? (Je ne pense pas que quelqu'un sera en colère de voir son image ici, mais il est juste de citer la source)
AL

Réponses:


16

Fortran

D'accord, j'utilise un format d'image obscur appelé FITS qui est utilisé pour l'astronomie. Cela signifie qu'il existe une bibliothèque Fortran pour lire et écrire ces images. En outre, ImageMagick et Gimp peuvent tous deux lire / écrire des images FITS.

L'algorithme que j'utilise est basé sur le tramage "Sierra Lite", mais avec deux améliorations:
a) Je réduit l'erreur propagée d'un facteur 4/5.
b) J'introduis une variation aléatoire dans la matrice de diffusion tout en gardant sa somme constante.
Ensemble, ils éliminent presque complètement les schémas observés dans l'exemple des PO.

En supposant que la bibliothèque CFITSIO soit installée, compilez avec

gfortran -lcfitsio dither.f90

Les noms de fichiers sont codés en dur (cela ne pouvait pas être dérangé pour résoudre ce problème).

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

Exemple de sortie pour l'image de chiot dans OPs post:
Dithered picture of puppy
OPs exemple de sortie:
OPs dithered picture of puppy


Cela a l'air vraiment bien, pourrait être imbattable pour la qualité
aditsu

Merci! Je ne sais pas si c'est imbattable, mais ça va être difficile (très subjectif) de juger cela par rapport à d'autres bons algorithmes.
semi-extrinsèque

1
Je sais que je joue au golf en abusant de la compatibilité descendante, mais il semble que vous en abusiez en standard. Ce code me fait pleurer.
Kyle Kanos

@KyleKanos Je suis toujours heureux quand mon code fait pleurer quelqu'un: p Sur le sujet cependant, qu'est-ce qui est horrible ici? Oui, j'aurais pu utiliser "aucun implicite", mais où est le plaisir là-dedans? Je l'utilise pour le codage sérieux au travail, mais pas pour le golf. Et je suis définitivement d'accord que l'API de la bibliothèque CFITSIO est complètement horrible (ftppre () sort une image FITS en simple précision réelle, ftpprj () sort une image en précision double entier, etc.) mais c'est la rétrocompatibilité F77 pour vous.
semi-extrinsèque

1
D'accord, donc la plupart d'entre eux n'étaient que moi bâclée. Je l'ai amélioré. La critique constructive est toujours appréciée :)
semi-extrinsèque

34

GraphicsMagick / ImageMagick

Dither commandé:

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

Avant de me plaindre de mon utilisation d'un "algorithme établi", veuillez lire le ChangeLog pour GraphicsMagick et ImageMagick pour avril 2003 où vous verrez que j'ai implémenté l'algorithme dans ces applications. En outre, la combinaison de "-gamma .45455" avec "-ordered-dither" est nouvelle.

Le "-gamma .45455" veille à ce que l'image soit trop claire. Le paramètre "all" n'est nécessaire qu'avec GraphicsMagick.

Il y a des bandes car il n'y a que 17 niveaux de gris dans une image de tramage ordonné 4x4. L'apparence des bandes peut être réduite en utilisant un tramage ordonné 8x8 qui a 65 niveaux.

Voici l'image originale, la sortie tramée ordonnée 4x4 et 8x8 et la sortie à seuil aléatoire: entrez la description de l'image ici

Je préfère la version de tramage ordonné, mais j'inclus la version à seuil aléatoire pour être complète.

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

Le "10x90%" signifie rendre en dessous de 10% des pixels d'intensité en noir pur et au-dessus de 90% en blanc pur, pour éviter d'avoir quelques taches solitaires dans ces zones.

Il est probablement intéressant de noter que les deux sont aussi économes en mémoire que possible. Il n'y a pas non plus de diffusion, ils fonctionnent donc un pixel à la fois, même lors de l'écriture de blocs de tramage ordonné, et n'ont besoin de rien savoir sur les pixels voisins. ImageMagick et GraphicsMagick traitent une ligne à la fois, mais ce n'est pas nécessaire pour ces méthodes. Les conversions de tramage ordonné prennent moins de 0,04 seconde en temps réel sur mon ancien ordinateur x86_64.


31
"Avant de vous plaindre de mon utilisation d'un" algorithme établi ", veuillez lire le ChangeLog pour GraphicsMagick et ImageMagick pour avril 2003 où vous verrez que j'ai implémenté l'algorithme dans ces applications." +1 pour la joue pure.
Joe Z.

22

Je m'excuse pour le style de code, j'ai jeté cela ensemble en utilisant certaines bibliothèques que nous venons de construire dans ma classe java, et il y a un mauvais cas de copier-coller et de nombres magiques. L'algorithme sélectionne des rectangles aléatoires dans l'image et vérifie si la luminosité moyenne est supérieure dans l'image tramée ou l'image d'origine. Il active ou désactive ensuite un pixel pour rapprocher les luminosités, en choisissant de préférence des pixels plus différents de l'image d'origine. Je pense que cela fait un meilleur travail en faisant ressortir des détails fins comme les cheveux du chiot, mais l'image est plus bruyante car elle essaie de faire ressortir les détails même dans les zones sans aucun.

entrez la description de l'image ici

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

Je suppose que c'est déterministe? Si oui, à quelle vitesse est-ce?
Οurous

C'est aléatoire et cela prend environ 3 secondes sur mon ordinateur.
QuadmasterXLII

2
Bien que ce ne soit peut-être pas l'algorithme de plus grande fidélité, les résultats sont de l'art à eux seuls.
AJMansfield

4
J'aime vraiment le look de cet algorithme! Mais je pense que ça a l'air si bien en partie parce qu'il produit une texture à peu près similaire à la fourrure, et c'est un animal avec de la fourrure. Mais je ne suis pas sûr que ce soit vrai. Pourriez-vous publier une autre image, par exemple d'une voiture?
semi-extrinsèque

1
Je pense que c'est la meilleure réponse, à la fois en termes d'originalité de l'algorithme et en termes de génialité des résultats. J'aimerais aussi vraiment le voir fonctionner sur d'autres images.
Nathaniel

13

Ghostscript (avec peu d'aide d'ImageMagick)

Loin d'être mon «nouvel algorithme», mais, désolé, je n'ai pas pu y résister.

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 -

entrez la description de l'image ici

Bien sûr, cela fonctionne mieux sans contrainte de «même taille».


2
C'est hilarant. Je suis stupéfait par le fait que personne n'a commenté cette merveille à la Warhol.
Andreï Kostyrka

10

JAVA

Voici ma soumission. Prend une image JPG, calcule la luminosité pixel par pixel (merci à Bonan dans cette question SO), puis vérifie-la par rapport à un motif aléatoire pour savoir si le pixel résultant sera noir ou blanc. Les pixels les plus sombres seront toujours noirs et les pixels les plus lumineux seront toujours blancs pour préserver les détails de l'image.

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

}

Image traitée

Autres exemples:

Original Traité

Fonctionne également avec des images en couleur:

Image couleur Résultat


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 octets :)
Il utilise le format ASCII PGM (P2) sans ligne de commentaire, pour l'entrée et la sortie.

La méthode est très basique: elle additionne des carrés de 2 * 2 pixels, convertit dans la plage 0..4, puis utilise un motif correspondant de 4 bits pour générer 2 * 2 pixels en noir et blanc.
Cela signifie également que la largeur et la hauteur doivent être égales.

Échantillon:

chiot déterministe

Et un algorithme aléatoire en seulement 27 octets:

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

Il utilise le même format de fichier.

Échantillon:

chiot au hasard

Et enfin une approche mixte: tramage aléatoire avec un biais vers un damier; 44 octets:

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

Échantillon:

chiot mixte


2
La première est comparable à l'application "Flipnote Studio" de la Nintendo DSi.
BobTheAwesome

6

Java (1.4+)

Je ne sais pas si je réinvente la roue ici, mais je pense qu'elle peut être unique ...

avec des séquences aléatoires limitées

Avec des séquences aléatoires limitées

Tramage aléatoire pur

Tramage aléatoire pur

entrez la description de l'image ici

Image de la ville de la réponse Averroes

L'algorithme utilise le concept d'énergie de luminosité localisée et de normalisation pour conserver les caractéristiques. La version initiale a ensuite utilisé une gigue aléatoire pour produire un regard tramé sur des zones de luminosité similaire. Mais ce n'était pas si attrayant visuellement. Pour contrer cela, un ensemble limité de séquences aléatoires limitées sont mappées à la luminosité des pixels d'entrée bruts et les échantillons sont utilisés de manière itérative et répétée pour produire des arrière-plans d'aspect tramé.

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
Très agréable. Cela donne certainement un effet différent de celui des autres réponses jusqu'à présent.
Geobits

@Geobits Oui, cela m'a surpris de son efficacité. Cependant, je ne sais pas si je l'appellerais un tramage car il produit une sortie assez différente visuellement
Moogie

Cela semble tout à fait unique.
qwr

5

Python

L'idée est la suivante: l'image est divisée en n x ntuiles. Nous calculons la couleur moyenne de chacune de ces tuiles. Ensuite, nous mappons la plage de couleurs 0 - 255sur la plage 0 - n*nqui nous donne une nouvelle valeur v. Ensuite, nous colorions tous les pixels de cette tuile en noir et colorions au hasard les vpixels de cette tuile en blanc. C'est loin d'être optimal mais nous donne quand même des résultats reconnaissables. Selon la résolution, cela fonctionne généralement mieux à n=2ou n=3. Pendant que n=2vous y êtes, vous pouvez déjà trouver des artefacts de la 'profondeur de couleur simulée, au cas oùn=3 cela pourrait déjà devenir quelque peu flou. J'ai supposé que les images devraient rester de la même taille, mais vous pouvez bien sûr également utiliser cette méthode et simplement doubler / tripler la taille de l'image générée afin d'obtenir plus de détails.

PS: Je sais que je suis un peu en retard à la fête, je me souviens que je n'avais aucune idée quand le challenge a commencé mais maintenant je viens d'avoir cette vague cérébrale =)

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

Résultats:

n=2:

entrez la description de l'image ici

n=3:

entrez la description de l'image ici


3

Tout format de fichier que vous voulez est très bien.

Définissons un format de fichier théorique très compact pour cette question car tous les formats de fichiers existants ont trop de surcharge pour écrire une réponse rapide.

Laissez les quatre premiers octets du fichier image définir la largeur et la hauteur de l'image en pixels, respectivement:

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

suivi d' w * hoctets de valeurs de niveaux de gris de 0 à 255:

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

Ensuite, nous pouvons définir un morceau de code en Python (145 octets) qui prendra cette image et fera:

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

qui "trament" en renvoyant du blanc ou du noir avec une probabilité égale à la valeur en niveaux de gris de ce pixel.


Appliqué sur l'exemple d'image, il donne quelque chose comme ceci:

chien tergiversé

Ce n'est pas trop joli, mais il semble très similaire lorsqu'il est réduit dans un aperçu, et pour seulement 145 octets de Python, je ne pense pas que vous puissiez faire beaucoup mieux.


Pouvez-vous partager un exemple? Je crois que c'est un tramage aléatoire, et les résultats ne sont pas les plus propres ... belle photo de profil
qwr

Il s'agit en fait d'un tramage aléatoire, et je fais un exemple de votre exemple d'image pour le moment.
Joe Z.

2
Je pense qu'il pourrait bénéficier d'un boost de contraste. Je ne connais pas le python, mais je suppose que random.randint (0,255) sélectionne un nombre aléatoire entre 0 et 255. Essayez de limiter entre 55 et 200, ce qui forcera toutes les nuances en dehors de cette plage à être du noir ou du blanc pur. Avec de nombreuses images, vous pouvez obtenir une bonne image saisissante sans tramage, juste un simple seuil. (Une augmentation aléatoire + du contraste donnerait une image intermédiaire entre votre image actuelle et un seuil simple.)
Level River St

Je pense que le tramage aléatoire devrait être appelé tramage Geiger (car il ressemble à la sortie d'un compteur Geiger). Qui est d'accord?
Joe Z.

1
C'est presque exactement ce que font ImageMagick et GraphicsMagick avec l'option "-random-threshold" que j'ai ajoutée avec "-ordered-dither" il y a des années (ajoutée à ma réponse). Encore une fois, augmenter le gamma aide à obtenir la bonne intensité. Je suis d'accord avec la suggestion de "tramage Geiger".
Glenn Randers-Pehrson

3

Cobra

Prend un fichier PNG / BMP 24 bits ou 32 bits (JPG produit une sortie avec quelques gris). Il est également extensible aux fichiers contenant de la couleur.

Il utilise ELA à vitesse optimisée pour tramer l'image en couleur 3 bits, qui reviendra en noir / blanc lorsque vous recevrez votre image de test.

Ai-je mentionné que c'est vraiment rapide?

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)

Chien

Des arbres


Pour réduire la répétition, avez-vous envisagé de créer une variable temporaire colet de laisser la image.setPixel(x,y,col)jusqu'à la fin?
joeytwiddle

3
Qu'est-ce que l'image des arbres?
AJMansfield

Il a l'air bien et fournit également un exemple de ce travail avec les couleurs.
7urous

2

Java

Code de bas niveau, utilisant PNGJ et un ajout de bruit plus une diffusion de base. Cette implémentation nécessite une source PNG 8 bits en niveaux de gris.

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

}

(Ajoutez ce pot à votre chemin de construction si vous voulez l'essayer).

entrez la description de l'image ici

En prime: il est extrêmement efficace dans l'utilisation de la mémoire (il ne stocke que trois lignes) et peut donc être utilisé pour des images énormes.


Nitpick: Je pense que "utilisé pour des images énormes" n'est pas si important (avez-vous déjà vu un PNG> 8 Go en niveaux de gris?), Mais "utilisé par exemple sur des appareils embarqués" est un point beaucoup plus important.
semi-extrinsèque

Je l'aime bien mais ça a l'air un peu flou sur les bords, il me semble.
BobTheAwesome

1

Java

Juste un algorithme simple basé sur RNG, plus une logique pour traiter les images en couleur. A une probabilité b de définir un pixel donné sur blanc, sinon il le définit sur noir; où b est la luminosité d'origine de ce 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();
    }
}

Voici un résultat potentiel pour l'image du chien:

entrez la description de l'image ici


Pourquoi n'ajoutez-vous pas l'explication en haut plutôt qu'en bas où personne ne va la lire? J'aime vraiment cette idée =)
flawr
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.