Dither a Grayscale Image

23

Geser gambar skala abu-abu menjadi hitam putih murni dengan algoritme Anda sendiri.

Panduan: Anda harus membuat algoritma baru Anda sendiri. Anda tidak dapat menggunakan algoritme yang sudah ada sebelumnya (mis. Floyd-Steinburg) tetapi Anda dapat menggunakan teknik umum. Program Anda harus dapat membaca gambar dan menghasilkan gambar dengan ukuran yang sama. Ini adalah kontes popularitas, jadi siapa pun yang menghasilkan yang terbaik (paling dekat dengan aslinya) dan paling kreatif (ditentukan oleh suara) menang. Bonus jika kodenya pendek, meskipun ini tidak perlu.

Anda dapat menggunakan gambar skala abu-abu apa pun yang Anda inginkan sebagai input, ukurannya harus lebih besar dari 300x300. Semua format file baik-baik saja.

Input contoh:

anak anjing

Contoh output:

ragu-ragu

Ini adalah pekerjaan yang cukup bagus, tetapi masih ada garis dan pola yang terlihat.

qwr
sumber
4
Memberi +1 untuk tantangan yang menarik, tapi saya pikir ini akan jauh lebih baik sebagai [kode-golf] (dengan spec) atau kriteria lain yang sepenuhnya objektif.
Gagang Pintu
2
Masalah dengan ukuran kode, kecepatan, dan penggunaan memori adalah bahwa Anda akan memerlukan ambang objektif untuk seberapa dapat dikenali hasilnya agar jawaban itu valid, yang juga sangat tidak mungkin. Kontes popularitas memang masuk akal, tetapi tanpa batasan pada kode tidak ada insentif bagi orang untuk berpikir di luar kotak. Saya lebih suka memilih jawaban yang cerdas daripada yang memberikan hasil terbaik karena hanya menerapkan algoritma yang ada. Tetapi Anda saat ini memberi insentif kepada yang terakhir.
Martin Ender
3
Garis antara algoritma dan tekniknya terlalu tipis untuk menentukan sisi mana sesuatu jatuh.
Peter Taylor
2
Saya pikir akan jauh lebih mudah untuk membandingkan hasil jika mereka semua menunjukkan hasil dari gambar yang sama.
joeytwiddle
3
Bisakah Anda menambahkan sumber gambar? (Saya tidak berpikir seseorang akan marah untuk melihat gambarnya di sini, tetapi adil untuk mengutip sumbernya)
AL

Jawaban:

16

Fortran

Oke, saya menggunakan format gambar tidak jelas yang disebut FITS yang digunakan untuk astronomi. Ini berarti ada perpustakaan Fortran untuk membaca dan menulis gambar seperti itu. ImageMagick dan Gimp juga dapat membaca / menulis gambar FIT.

Algoritma yang saya gunakan didasarkan pada dithering "Sierra Lite", tetapi dengan dua peningkatan:
a) Saya mengurangi kesalahan yang diperbanyak dengan faktor 4/5.
b) Saya memperkenalkan variasi acak dalam matriks difusi sambil menjaga jumlah konstannya.
Bersama-sama ini hampir sepenuhnya menghilangkan pola yang terlihat dalam contoh OPs.

Dengan asumsi Anda memiliki perpustakaan CFITSIO diinstal, kompilasi dengan

gfortran -lcfitsio dither.f90

Nama-nama file tersebut hard-coded (tidak bisa diganggu untuk memperbaikinya).

Kode:

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

Contoh output untuk gambar anak anjing di pos
Gambar puppy bingung
OP : contoh output OP:
OP ragu-ragu gambar anak anjing

semi ekstrinsik
sumber
Ini terlihat sangat bagus, mungkin tidak ada
duanya
Terima kasih! Saya tidak tahu bahwa itu tidak ada duanya, tetapi akan sulit (sangat subyektif) untuk menilai ini terhadap algoritma yang baik lainnya.
semi-ekstrinsik
1
Saya tahu saya menggunakan kode golf dengan menyalahgunakan kompatibilitas, tetapi sebenarnya Anda menyalahgunakannya sebagai standar. Kode ini sebenarnya membuatku menangis.
Kyle Kanos
@KyleKanos Saya selalu senang ketika kode saya membuat seseorang menangis: p Pada topik, apa yang mengerikan di sini? Ya, saya bisa menggunakan "tidak ada yang implisit", tetapi di mana kesenangannya? Saya menggunakannya untuk pengkodean serius di tempat kerja, tetapi tidak untuk golf. Dan saya benar-benar setuju bahwa pustaka CFITSIO API benar-benar mengerikan (ftppre () menampilkan gambar FITS dalam presisi nyata tunggal, ftpprj () menghasilkan gambar dalam presisi bilangan bulat ganda, dll.) Tetapi itu adalah kompatibilitas mundur F77 untuk Anda.
semi-ekstrinsik
1
Oke, jadi kebanyakan dari mereka hanya saya yang ceroboh. Saya memperbaikinya. Kritik konstruktif selalu dihargai :)
semi-ekstrinsik
34

GraphicsMagick / ImageMagick

Dipesan di muka:

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

Sebelum mengeluh tentang penggunaan "algoritma mapan" saya, harap baca ChangeLog untuk GraphicsMagick dan ImageMagick untuk April 2003 di mana Anda akan melihat bahwa saya menerapkan algoritme dalam aplikasi tersebut. Juga, kombinasi "-gamma .45455" dengan "-ordered-dither" adalah baru.

"-Gamma .45455" menangani gambar yang terlalu terang. Parameter "semua" hanya diperlukan dengan GraphicsMagick.

Ada garis tepi karena hanya ada 17 tingkat abu-abu dalam gambar gentar beraturan 4x4. Tampilan pita dapat dikurangi dengan menggunakan 8x8 teratur-dither yang memiliki 65 level.

Berikut adalah gambar asli, output dithered yang dipesan 4x4 dan 8x8 dan output ambang-acak: masukkan deskripsi gambar di sini

Saya lebih suka versi memerintahkan-gentar, tetapi saya termasuk versi ambang batas acak untuk kelengkapan.

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

"10x90%" berarti membuat piksel intensitas di bawah 10 persen sebagai hitam murni dan di atas 90 persen sebagai putih murni, untuk menghindari memiliki beberapa bintik kesepian di area tersebut.

Mungkin perlu dicatat bahwa keduanya seefisien-memori seperti yang seharusnya. Juga tidak ada difusi, sehingga mereka bekerja satu piksel pada satu waktu, bahkan ketika menulis blok teratur-gentar, dan tidak perlu tahu apa-apa tentang piksel tetangga. ImageMagick dan GraphicsMagick memproses satu baris setiap kali, tetapi tidak perlu untuk metode ini. Konversi terurut-surut waktu kurang dari 0,04 detik waktu nyata di komputer x86_64 lama saya.

Glenn Randers-Pehrson
sumber
31
"Sebelum mengeluh tentang penggunaan" algoritma mapan "saya, harap baca ChangeLog untuk GraphicsMagick dan ImageMagick untuk April 2003 di mana Anda akan melihat bahwa saya menerapkan algoritme dalam aplikasi tersebut." +1 untuk pipi tipis.
Joe Z.
22

Saya minta maaf untuk gaya kode, saya melemparkan ini bersama-sama menggunakan beberapa perpustakaan yang baru saja kita bangun di kelas java saya, dan memiliki kasus buruk copy-paste dan angka ajaib. Algoritme memilih persegi panjang acak dalam gambar, dan memeriksa apakah kecerahan rata-rata lebih besar pada gambar ragu-ragu atau gambar asli. Ini kemudian menghidupkan atau mematikan piksel untuk membawa kecerahan lebih dekat dalam garis, lebih disukai memilih piksel yang lebih berbeda dari gambar asli. Saya pikir itu melakukan pekerjaan yang lebih baik mengeluarkan detail tipis seperti rambut anak anjing, tetapi gambar lebih ribut karena mencoba mengeluarkan detail bahkan di daerah yang tidak ada.

masukkan deskripsi gambar di sini

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
sumber
Saya menganggap ini deterministik? Jika demikian, seberapa cepat itu?
Surous
Ini acak, dan membutuhkan sekitar 3 detik di komputer saya.
QuadmasterXLII
2
Meskipun mungkin bukan algoritma kesetiaan terbesar, hasilnya adalah seni sendiri.
AJMansfield
4
Saya sangat suka tampilan algoritma ini! Tapi saya pikir mungkin itu terlihat sangat baik sebagian karena menghasilkan tekstur yang kira-kira mirip dengan bulu, dan ini adalah hewan dengan bulu. Tetapi saya tidak sepenuhnya yakin ini benar. Bisakah Anda memposting gambar lain misalnya mobil?
semi-ekstrinsik
1
Saya pikir ini adalah jawaban terbaik, baik dari segi keaslian algoritma dan dalam hal kedahsyatan hasil. Saya juga sangat ingin melihatnya berjalan pada beberapa gambar lain juga.
Nathaniel
13

Ghostscript (dengan sedikit bantuan ImageMagick)

Jauh dari menjadi 'algoritma baru saya sendiri', tetapi, maaf, tidak bisa menolaknya.

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 -

masukkan deskripsi gambar di sini

Tentu saja itu bekerja lebih baik tanpa pengekangan 'ukuran yang sama'.

pengguna2846289
sumber
2
Ini sangat lucu. Saya terpana oleh kenyataan bahwa tidak ada yang berkomentar tentang keajaiban gaya Warhol ini.
Andreï Kostyrka
10

JAWA

Ini kiriman saya. Mengambil gambar JPG, menghitung luminositas piksel per piksel (terima kasih kepada Bonan dalam pertanyaan SO ini ) dan kemudian memeriksanya dengan pola acak untuk mengetahui apakah piksel yang dihasilkan akan hitam atau putih. Piksel yang lebih gelap akan selalu hitam dan piksel yang paling cerah akan selalu berwarna putih untuk menjaga detail gambar.

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

}

Gambar yang diproses

Contoh lain:

Asli Diproses

Juga berfungsi dengan gambar penuh warna:

Gambar berwarna Hasil

Averroes
sumber
9

CJam

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

95 byte :)
Menggunakan format ASCII PGM (P2) tanpa garis komentar, baik untuk input maupun output.

Metode ini sangat mendasar: menambahkan kuadrat 2 * 2 piksel, mengkonversi ke kisaran 0,4, kemudian menggunakan pola 4 bit yang sesuai untuk menghasilkan 2 * 2 piksel hitam-putih.
Itu juga berarti lebar dan tinggi harus genap.

Mencicipi:

anak anjing deterministik

Dan algoritma acak hanya dalam 27 byte:

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

Ini menggunakan format file yang sama.

Mencicipi:

anak anjing acak

Dan akhirnya pendekatan campuran: dithering acak dengan bias terhadap pola kotak-kotak; 44 byte:

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

Mencicipi:

anak anjing campuran

aditsu
sumber
2
Yang pertama dapat dibandingkan dengan aplikasi "Flipnote Studio" Nintendo DSi.
BobTheAwesome
6

Java (1.4+)

Saya tidak yakin apakah saya menemukan kembali roda di sini tapi saya pikir itu mungkin unik ...

dengan urutan acak terbatas

Dengan urutan acak terbatas

Dithering acak murni

Dithering acak murni

masukkan deskripsi gambar di sini

Gambar kota dari jawaban Averroes

Algoritme menggunakan konsep energi luminositas lokal dan normalisasi untuk mempertahankan fitur. Versi awal kemudian menggunakan jitter acak untuk menghasilkan tampilan bingung atas area luminositas serupa. Namun itu tidak begitu menarik secara visual. Untuk mengatasi hal ini, serangkaian terbatas urutan acak terbatas dipetakan ke luminositas piksel input mentah dan sampel digunakan berulang dan berulang-ulang menghasilkan latar belakang tampak ragu-ragu.

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
sumber
3
Sangat bagus. Itu pasti memberi efek berbeda dari jawaban lain sejauh ini.
Geobits
@ Geobits Ya, itu mengejutkan saya betapa efektifnya itu. Namun saya tidak yakin apakah saya akan menyebutnya sebagai dithering karena menghasilkan output yang sangat berbeda secara visual
Moogie
Itu memang terlihat cukup unik.
qwr
5

Python

Idenya adalah sebagai berikut: Gambar akan dibagi menjadi n x nubin. Kami menghitung warna rata-rata setiap ubin tersebut. Lalu kami memetakan rentang warna 0 - 255ke kisaran 0 - n*nyang memberi kami nilai baru v. Lalu kami mewarnai semua piksel dari ubin hitam itu, dan vpiksel warna secara acak di dalam ubin putih itu. Ini jauh dari optimal tetapi masih memberi kita hasil yang dapat dikenali. Bergantung pada resolusinya, biasanya berfungsi paling baik di n=2atau n=3. Sementara di dalam n=2Anda sudah dapat menemukan artefak dari 'kedalaman warna yang disimulasikan, kalau- n=3kalau sudah bisa agak kabur. Saya berasumsi bahwa gambar harus tetap dengan ukuran yang sama, tetapi Anda tentu saja dapat juga menggunakan metode ini dan hanya menggandakan / tiga kali lipat ukuran gambar yang dihasilkan untuk mendapatkan rincian lebih lanjut.

PS: Saya tahu bahwa saya agak terlambat ke pesta, saya ingat saya tidak punya ide ketika tantangan dimulai tetapi sekarang hanya gelombang otak ini =)

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

Hasil:

n=2:

masukkan deskripsi gambar di sini

n=3:

masukkan deskripsi gambar di sini

cacat
sumber
3

Format file apa pun yang Anda inginkan baik-baik saja.

Mari kita tentukan format file teoretis yang sangat ringkas untuk pertanyaan ini karena format file yang ada memiliki terlalu banyak overhead untuk menulis jawaban cepat.

Biarkan empat byte pertama dari file gambar menentukan lebar dan tinggi gambar dalam piksel, masing-masing:

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

diikuti oleh w * hbyte nilai grayscale dari 0 hingga 255:

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

Kemudian, kita dapat mendefinisikan sepotong kode dengan Python (145 byte) yang akan mengambil gambar ini dan melakukan:

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

yang "dithers" dengan mengembalikan putih atau hitam dengan probabilitas sama dengan nilai skala abu-abu dari piksel itu.


Diterapkan pada gambar sampel, itu memberikan sesuatu seperti ini:

anjing bimbang

Ini tidak terlalu cantik, tetapi memang terlihat sangat mirip ketika diperkecil dalam pratinjau, dan hanya untuk 145 byte Python, saya tidak berpikir Anda bisa menjadi lebih baik.

Joe Z.
sumber
Bisakah Anda membagikan contoh? Saya percaya ini adalah dithering acak, dan hasilnya bukan yang terbersih ... gambar profil yang bagus
qwr
Ini memang dithering acak, dan saya sedang membuat contoh gambar sampel Anda saat ini.
Joe Z.
2
Saya pikir itu mungkin mendapat manfaat dari peningkatan kontras. Saya tidak tahu python, tetapi saya berasumsi random.randint (0,255) memilih angka acak antara 0 dan 255. Coba batasi antara katakanlah 55 dan 200, yang akan memaksa nuansa apa pun di luar rentang itu menjadi hitam atau putih murni. Dengan banyak gambar, Anda bisa mendapatkan gambar yang bagus dan mencolok tanpa dithering, hanya ambang sederhana. (Random + kontras boost akan memberikan gambar antara antara gambar Anda saat ini dan ambang batas sederhana.)
Level River St
Saya pikir dithering acak harus disebut Geiger dithering (karena terlihat seperti output penghitung Geiger). Siapa yang setuju?
Joe Z.
1
Itu hampir persis apa yang dilakukan ImageMagick dan GraphicsMagick dengan opsi "-random-threshold" yang saya tambahkan bersama dengan "-ordered-dither" tahun yang lalu (ditambahkan ke jawaban saya). Sekali lagi, menabrak gamma membantu mendapatkan intensitas yang tepat. Saya setuju dengan saran "Geiger dithering".
Glenn Randers-Pehrson
3

Kobra

Mengambil file PNG / BMP 24-bit atau 32-bit (JPG menghasilkan output dengan beberapa abu-abu di dalamnya). Itu juga dapat diperluas ke file yang mengandung warna.

Ia menggunakan ELA yang dioptimalkan kecepatan untuk menggeser gambar menjadi warna 3-bit, yang akan kembali sebagai hitam / putih saat diberikan gambar uji Anda.

Apakah saya menyebutkan bahwa itu sangat cepat?

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)

Anjing

Pohon

Suram
sumber
Untuk mengurangi pengulangan, sudahkah Anda mempertimbangkan untuk membuat variabel sementara col, dan membiarkan image.setPixel(x,y,col)sampai akhir?
joeytwiddle
3
Ada apa dengan gambar pohon?
AJMansfield
Itu terlihat bagus, dan memberikan contoh ini bekerja dengan warna juga.
Kamis
2

Jawa

Kode tingkat rendah, menggunakan PNGJ dan penambahan noise plus difusi dasar. Implementasi ini membutuhkan sumber PNG 8-bit skala abu-abu.

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

}

(Tambahkan toples ini ke jalur build Anda jika Anda ingin mencobanya).

masukkan deskripsi gambar di sini

Sebagai bonus: ini sangat efisien dalam penggunaan memori (hanya menyimpan tiga baris) sehingga dapat digunakan untuk gambar besar.

leonbloy
sumber
Nitpick: Saya akan berpikir "digunakan untuk gambar besar" tidak begitu penting (pernahkah Anda melihat PNG skala abu-abu> 8 GB?), Tetapi "digunakan pada perangkat yang disematkan" adalah titik yang jauh lebih menonjol.
semi-ekstrinsik
Saya suka tapi kelihatannya agak kabur di sekitar pinggirannya, metinks.
BobTheAwesome
1

Jawa

Hanya algoritma berbasis RNG sederhana, ditambah beberapa logika untuk berurusan dengan gambar berwarna. Memiliki probabilitas b untuk mengatur setiap piksel yang diberikan menjadi putih, menetapkannya menjadi hitam sebaliknya; di mana b adalah kecerahan asli piksel itu.

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

Berikut adalah hasil potensial untuk gambar anjing:

masukkan deskripsi gambar di sini

SuperJedi224
sumber
Mengapa Anda tidak menambahkan penjelasan ke atas, bukan bagian bawah di mana tidak ada yang akan membacanya? Saya sangat suka ide itu =)
flawr