Pontilhar uma imagem em escala de cinza

23

Coloque uma imagem em escala de cinza em preto e branco puro com seu próprio algoritmo.

Diretrizes: você deve criar seu próprio novo algoritmo. Você não pode usar algoritmos preexistentes (por exemplo, Floyd-Steinburg), mas pode usar a técnica geral. Seu programa deve ser capaz de ler uma imagem e produzir uma imagem do mesmo tamanho. Este é um concurso de popularidade, e quem produz o melhor (o mais próximo do original) e o mais criativo (determinado por votos) vence. Bônus se o código for curto, embora isso não seja necessário.

Você pode usar qualquer imagem em escala de cinza que desejar como entrada, que deve ser maior que 300x300. Qualquer formato de arquivo está correto.

Exemplo de entrada:

cachorro

Exemplo de saída:

pontilhado

Este é um trabalho muito bom, mas ainda existem linhas e padrões visíveis.

qwr
fonte
4
+1 para um desafio interessante, mas acho que isso seria muito melhor como um [código-golfe] (com uma especificação) ou algum outro critério completamente objetivo.
Maçaneta da porta
2
O problema com o tamanho do código, a velocidade e o uso da memória é que você precisaria de um limite objetivo para saber o quão reconhecível deve ser o resultado para que a resposta seja válida, o que também é bastante impossível. O concurso de popularidade faz sentido, mas sem nenhuma restrição ao código, não há incentivo para as pessoas pensarem fora da caixa. Eu prefiro aprovar uma resposta inteligente do que dar o melhor resultado, porque acabou de implementar um algoritmo existente. Mas você está atualmente incentivando o último.
Martin Ender
3
A linha entre um algoritmo e sua técnica é muito fina para determinar de que lado algo cai.
Peter Taylor
2
Eu acho que seria muito mais fácil comparar os resultados se todos mostrassem resultados da mesma imagem.
Joeytwiddle
3
Você pode adicionar a fonte da imagem? (Eu não acho que alguém vai ficar com raiva de ver a sua his / her imagem aqui, mas é justo citar a fonte)
AL

Respostas:

16

Fortran

Ok, estou usando um formato de imagem obscuro chamado FITS, usado para astronomia. Isso significa que existe uma biblioteca Fortran para ler e escrever essas imagens. Além disso, o ImageMagick e o Gimp podem ler / gravar imagens FITS.

O algoritmo usado é baseado no pontilhamento "Sierra Lite", mas com duas melhorias:
a) reduzo o erro propagado por um fator 4/5.
b) Eu introduzo uma variação aleatória na matriz de difusão, mantendo sua soma constante.
Juntos, eles quase eliminam completamente os padrões vistos no exemplo dos OPs.

Supondo que você tenha a biblioteca CFITSIO instalada, compile com

gfortran -lcfitsio dither.f90

Os nomes dos arquivos são codificados (não se pode incomodar em corrigir isso).

Código:

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

Exemplo de saída para a imagem do filhote de cachorro em OPs post:
Imagem pontilhada de cachorro
OPs example output:
OP imagem pontilhada de filhote de cachorro

semi-extrínseco
fonte
Isso parece muito bom, pode ser imbatível para a qualidade
aditsu
Obrigado! Não sei se é imbatível, mas será difícil (muito subjetivo) julgar isso contra outros bons algoritmos.
semi-extrínseco
1
Sei que codifico o código abusando da compatibilidade com versões anteriores, mas na verdade parece que você abusou dele como padrão. Este código está realmente me fazendo chorar.
Kyle Kanos #
@KyleKanos Fico sempre feliz quando meu código faz alguém chorar: p No tópico, o que especificamente é horrível aqui? Sim, eu poderia ter usado "nenhum implícito", mas onde está a graça nisso? Eu o uso para codificação séria no trabalho, mas não para golfe. E eu definitivamente concordo que a API da biblioteca CFITSIO é completamente horrível (ftppre () gera uma imagem FITS com precisão real única, ftpprj () gera uma imagem com precisão inteira dupla, etc.), mas essa é a compatibilidade retroativa do F77 para você.
semi-extrínseco
1
Ok, então a maioria desses eram apenas eu sendo desleixada. Eu melhorei isso. Crítica construtiva é sempre apreciada :)
semi-extrínseca
34

GraphicsMagick / ImageMagick

Pontilhamento ordenado:

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

Antes de reclamar sobre o uso de um "algoritmo estabelecido", leia o ChangeLog for GraphicsMagick e ImageMagick para abril de 2003, onde você verá que eu implementei o algoritmo nesses aplicativos. Além disso, a combinação de "-gamma .45455" com "-ordered-dither" é nova.

O "-gamma .45455" cuida da imagem ser muito clara. O parâmetro "all" é necessário apenas com GraphicsMagick.

Há faixas porque existem apenas 17 níveis de cinza em uma imagem pontilhada 4x4. A aparência das faixas pode ser reduzida usando um pontilhado 8x8 com 65 níveis.

Aqui está a imagem original, a saída pontilhada ordenada 4x4 e 8x8 e a saída com limite aleatório: insira a descrição da imagem aqui

Prefiro a versão com pontilhamento ordenado, mas estou incluindo a versão com limite aleatório para fins de completude.

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

O "10x90%" significa renderizar abaixo de 10% de pixels de intensidade em preto puro e acima de 90% em branco puro, para evitar ter algumas manchas solitárias nessas áreas.

Provavelmente vale a pena notar que ambos são tão eficientes quanto possível à memória. Como não há difusão, eles trabalham um pixel de cada vez, mesmo quando escrevem blocos de pontilhamento ordenado, e não precisam saber nada sobre os pixels vizinhos. ImageMagick e GraphicsMagick processam uma linha por vez, mas não é necessário para esses métodos. As conversões de pontilhamento ordenado levam menos de 0,04 segundo em tempo real no meu antigo computador x86_64.

Glenn Randers-Pehrson
fonte
31
"Antes de reclamar sobre o uso de um" algoritmo estabelecido ", leia o ChangeLog for GraphicsMagick e ImageMagick para abril de 2003, onde você verá que eu implementei o algoritmo nesses aplicativos". +1 para bochecha pura.
Joe Z.
22

Peço desculpas pelo estilo do código, juntei isso usando algumas bibliotecas que acabamos de criar na minha classe java, e há um caso ruim de copiar e colar e números mágicos. O algoritmo seleciona retângulos aleatórios na imagem e verifica se o brilho médio é maior na imagem pontilhada ou na imagem original. Em seguida, liga ou desliga um pixel para aproximar o brilho da linha, escolhendo preferencialmente pixels mais diferentes da imagem original. Eu acho que faz um trabalho melhor destacando detalhes finos como o cabelo do filhote, mas a imagem é mais barulhenta porque tenta mostrar detalhes mesmo em áreas sem nenhum.

insira a descrição da imagem aqui

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();
}
QuadmasterXLII
fonte
Entendo que isso é determinístico? Se sim, qual a velocidade?
Οurous
É aleatório e leva cerca de 3 segundos no meu computador.
QuadmasterXLII
2
Embora talvez não seja o algoritmo de maior fidelidade, os resultados são apenas arte.
AJMansfield
4
Eu realmente gosto da aparência desse algoritmo! Mas acho que talvez pareça tão bom, em parte, porque produz uma textura quase semelhante à do pêlo, e este é um animal com pêlo. Mas não tenho certeza absoluta de que isso seja verdade. Você poderia postar outra imagem, por exemplo, de um carro?
semi-extrínseco
1
Eu acho que essa é a melhor resposta, tanto em termos de originalidade do algoritmo quanto em termos de grandiosidade dos resultados. Eu também gostaria muito de vê-lo em outras imagens também.
Nathaniel #
13

Ghostscript (com pouca ajuda do ImageMagick)

Longe de ser meu 'novo algoritmo', mas, desculpe, não pude resistir.

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 -

insira a descrição da imagem aqui

É claro que funciona melhor sem restrição do 'mesmo tamanho'.

user2846289
fonte
2
Isso é hilário. Estou impressionado com o fato de ninguém ter comentado essa maravilha no estilo de Warhol.
Andreï Kostyrka
10

JAVA

Aqui está a minha submissão. Pega uma imagem JPG, calcula a luminosidade pixel por pixel (graças a Bonan nesta pergunta SO) e, em seguida, verifica-a em um padrão aleatório para saber se o pixel resultante será preto ou branco. Os pixels de Darkerst serão sempre pretos e os pixels mais brilhantes sempre serão brancos para preservar os detalhes da imagem.

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

}

Imagem processada

Outros exemplos:

Original Processado

Também funciona com imagens coloridas:

Imagem colorida Resultado

Averroes
fonte
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 :)
Ele usa o formato ASCII PGM (P2) sem linha de comentário, para entrada e saída.

O método é muito básico: soma quadrados de 2 * 2 pixels, converte para o intervalo 0..4 e depois usa um padrão correspondente de 4 bits para gerar 2 * 2 pixels em preto e branco.
Isso também significa que a largura e a altura devem ser uniformes.

Amostra:

filhote determinista

E um algoritmo aleatório em apenas 27 bytes:

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

Ele usa o mesmo formato de arquivo.

Amostra:

filhote aleatório

E, finalmente, uma abordagem mista: pontilhamento aleatório com tendência a um padrão quadriculado; 44 bytes:

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

Amostra:

filhote de cachorro misturado

aditsu
fonte
2
O primeiro é comparável ao aplicativo "Flipnote Studio" do Nintendo DSi.
21815 BobTheAwesome
6

Java (1.4 ou superior)

Não tenho certeza se estou reinventando a roda aqui, mas acho que ela pode ser única ...

com sequências aleatórias limitadas

Com sequências aleatórias limitadas

Pontilhamento aleatório puro

Pontilhamento aleatório puro

insira a descrição da imagem aqui

Imagem da cidade da resposta de Averroes

O algoritmo usa o conceito de energia de luminosidade localizada e normaliza para reter recursos. A versão inicial usou um jitter aleatório para produzir uma aparência distorcida sobre áreas de luminosidade semelhante. No entanto, não era tão visualmente atraente. Para contrariar isso, um conjunto limitado de sequências aleatórias limitadas é mapeado para a luminosidade bruta do pixel de entrada e as amostras são usadas iterativamente e repetidamente produzindo fundos com aparência pontilhada.

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

}
Moogie
fonte
3
Muito agradável. Definitivamente, dá um efeito diferente do que as outras respostas até agora.
Geobits
@ Geobits Sim, me surpreendeu o quão eficaz é. No entanto, eu não tenho certeza se eu chamaria isso de um pontilhado, pois produz uma saída bastante visualmente diferente
Moogie
Isso parece bastante único.
QWR
5

Python

A idéia é a seguinte: A imagem é dividida em n x nblocos. Calculamos a cor média de cada uma dessas peças. Em seguida, mapeamos o intervalo de cores 0 - 255para o intervalo 0 - n*nque nos fornece um novo valor v. Em seguida, colorimos todos os pixels desse bloco de preto e colorimos aleatoriamente os vpixels desse bloco de branco. Está longe de ser o ideal, mas ainda nos dá resultados reconhecíveis. Dependendo da resolução, geralmente funciona melhor n=2oun=3 . Enquanto n=2você já pode encontrar artefatos a partir da profundidade de cor simulada, caso n=3já possa ficar um pouco desfocado. Eu assumi que as imagens deveriam permanecer do mesmo tamanho, mas é claro que você também pode usar esse método e apenas dobrar / triplicar o tamanho da imagem gerada para obter mais detalhes.

PS: Eu sei que estou um pouco atrasado para a festa, lembro que não tinha nenhuma idéia quando o desafio começou, mas agora só tinha essa onda cerebral =)

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

Resultados:

n=2:

insira a descrição da imagem aqui

n=3:

insira a descrição da imagem aqui

flawr
fonte
3

Qualquer formato de arquivo desejado é bom.

Vamos definir um formato de arquivo teórico muito compacto para esta pergunta, já que qualquer um dos formatos de arquivo existentes possui sobrecarga demais para a qual responder rapidamente.

Deixe os quatro primeiros bytes do arquivo de imagem definirem a largura e a altura da imagem em pixels, respectivamente:

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

seguido por w * hbytes de valores em escala de cinza de 0 a 255:

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

Em seguida, podemos definir um trecho de código em Python (145 bytes) que obterá esta imagem 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')

que "se estraga" retornando branco ou preto com probabilidade igual ao valor da escala de cinza desse pixel.


Aplicado na imagem de amostra, fornece algo como isto:

cachorro pontudo

Não é muito bonito, mas parece muito semelhante quando reduzido em uma visualização e, para apenas 145 bytes de Python, não acho que você possa melhorar muito.

Joe Z.
fonte
Você pode compartilhar um exemplo? Eu acredito que este é um pontilhamento aleatório, e os resultados não são os mais limpos ... boa foto de perfil
qwr
Este é realmente um pontilhamento aleatório, e estou fazendo um exemplo da sua amostra de amostra no momento.
Joe Z.
2
Eu acho que pode se beneficiar de um aumento de contraste. Não conheço python, mas presumo que random.randint (0,255) está escolhendo um número aleatório entre 0 e 255. Tente limitar entre, digamos, 55 e 200, o que forçará qualquer sombra fora desse intervalo a ser preto ou branco puro. Com muitas fotos, você pode obter uma imagem excelente e impressionante, sem pontilhamento, apenas um limiar simples. (Random + impulso contraste daria uma imagem intermediária entre a sua imagem atual e limite simples.)
Nível River St
Eu acho que o pontilhamento aleatório deve ser chamado de pontilhamento Geiger (porque parece com a saída de um contador Geiger). Quem concorda?
Joe Z.
1
Isso é quase exatamente o que o ImageMagick e o GraphicsMagick fazem com a opção "-random-threshold" que eu adicionei junto com o "-ordered-dither" anos atrás (adicionado à minha resposta). Mais uma vez, aumentar a gama ajuda a obter a intensidade certa. Eu concordo com a sugestão "Geiger dithering".
Glenn Randers-Pehrson
3

Cobra

Toma um arquivo PNG / BMP de 24 ou 32 bits (o JPG produz saída com alguns tons de cinza). Também é extensível a arquivos que contêm cores.

Ele usa ELA com velocidade otimizada para pontilhar a imagem em cores de 3 bits, que retornarão em preto / branco quando receber a imagem de teste.

Eu mencionei que é muito rápido?

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)

Cachorro

Árvores

Furioso
fonte
Para reduzir a repetição, você já pensou em criar uma variável temporária cole deixar a opção image.setPixel(x,y,col)até o final?
Joeytwiddle
3
O que há com a imagem das árvores?
AJMansfield
Parece bom e fornece um exemplo disso trabalhando com cores também.
Οurous
2

Java

Código de baixo nível, usando PNGJ e uma adição de ruído mais difusão básica. Esta implementação requer uma fonte PNG de 8 bits em escala de cinza.

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

}

(Adicione este jar ao seu caminho de construção, se você quiser experimentá-lo).

insira a descrição da imagem aqui

Como um bônus: isso é extremamente eficiente no uso de memória (armazena apenas três linhas) e pode ser usado para imagens enormes.

leonbloy
fonte
Nitpick: Eu acho que "usado para imagens enormes" não é tão importante (você já viu um PNG> 8 GB em escala de cinza?), Mas "usado em dispositivos embutidos, por exemplo" é um ponto muito mais saliente.
semi-extrínseco
Eu gosto, mas parece um pouco borrado nas bordas, acho.
BobTheAwesome
1

Java

Apenas um algoritmo simples baseado em RNG, além de alguma lógica para lidar com imagens coloridas. Tem a probabilidade b de definir qualquer pixel para branco, define-o como preto; onde b é o brilho original desse 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();
    }
}

Aqui está um resultado potencial para a imagem do cão:

insira a descrição da imagem aqui

SuperJedi224
fonte
Por que você não adiciona a explicação ao topo, e não ao fundo, onde ninguém vai lê-la? Eu realmente gosto dessa idéia =)
flawr