Зображення відтінків сірого


23

Накресліть зображення сірого кольору в чисто чорно-білі за допомогою власного алгоритму.

Вказівки: Ви повинні придумати свій новий алгоритм. Ви не можете використовувати вже існуючі алгоритми (наприклад, Флойд-Штайнбург), але ви можете використовувати загальну техніку. Ваша програма повинна вміти читати зображення та створювати зображення однакового розміру. Це конкурс на популярність, тому той, хто виробляє найкращого (найближчого до оригіналу) та найкреативнішого (визначається голосами), виграє. Бонус, якщо код короткий, хоча це не обов'язково.

Ви можете використовувати будь-яке зображення сірого масштабу як вхідне, воно повинно бути більше 300x300. Будь-який формат файлу чудовий.

Приклад введення:

цуценя

Приклад виводу:

розрізнений

Це досить гарна робота, але все ще є видимі лінії та візерунки.


4
+1 для цікавого виклику, але я думаю, що це було б набагато краще, як [код-гольф] (зі специфікацією) або якийсь інший цілком об'єктивний критерій.
Дверна ручка

2
Проблема з розміром коду, швидкістю та використанням пам'яті полягає в тому, що вам знадобиться об'єктивний поріг того, наскільки розпізнаваним повинен бути результат, щоб відповідь була дійсною, що також зовсім неможливо. Конкурс на популярність має сенс, але без будь-якого обмеження коду, люди не стимулюють задуматися. Я вважаю за краще проголосувати розумною відповіддю, ніж найкращий результат, оскільки він просто реалізував існуючий алгоритм. Але ти зараз стимулюєш останнє.
Мартін Ендер

3
Рядок між алгоритмом та його технікою занадто тонкий, щоб визначити, на яку сторону щось падає.
Пітер Тейлор

2
Я думаю, було б набагато простіше порівняти результати, якби всі вони показали результати одного зображення.
joeytwiddle

3
Чи можете ви додати джерело зображення? (Я не думаю, що хтось розсердиться, побачивши його / її зображення тут, але справедливо цитувати джерело)
AL

Відповіді:


16

Фортран

Гаразд, я використовую неясний формат зображення під назвою FITS, який використовується для астрономії. Це означає, що існує бібліотека Fortran для читання та написання таких зображень. Також ImageMagick і Gimp можуть читати / писати зображення FITS.

Алгоритм, який я використовую, ґрунтується на "відшаруванні" Sierra Lite, але з двома вдосконаленнями:
а) я зменшую поширювану помилку на коефіцієнт 4/5.
б) Я ввожу випадкову зміну в матриці дифузії, зберігаючи її суму постійною.
Разом вони майже повністю ліквідують закономірності, наведені на прикладі ОП.

Припустимо, що у вас встановлена ​​бібліотека CFITSIO, компілюйте її

gfortran -lcfitsio dither.f90

Назви файлів жорстко закодовані (виправити це не вдалося).

Код:

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

Приклад виведення зображення цуценя в ОП:
Зніщене зображення цуценя
Опис прикладу ОП:
ОП малювали малюнок цуценя


Це виглядає справді добре, може бути неперевершеним за якістю
aditsu

Спасибі! Я не знаю, що це неперевершено, але судити це проти інших хороших алгоритмів буде важко (дуже суб'єктивно).
напівкрединне

1
Я знаю, що я гольф-код, зловживаючи зворотною сумісністю, але насправді це виглядає так, що ви його зловживаєте як стандарт. Цей код насправді змушує мене плакати.
Кайл Канос

@KyleKanos Я завжди радий, коли мій код змушує когось плакати: p На тему, що конкретно тут жахливо? Так, я міг би використати "неявну", але де в цьому весело? Я використовую його для серйозного кодування на роботі, але не для гольфу. І я безумовно погоджуюся, що API бібліотеки CFITSIO є абсолютно жахливим (ftppre () видає зображення FITS в єдиній реальній точності, ftpprj () видає зображення в подвійній цілорічній точності тощо), але це сумісність F77 назад для вас.
напів зовнішній

1
Гаразд, тому більшість із них були просто недбалими. Я його покращив. Конструктивна критика завжди цінується :)
напівкрединна

34

GraphicsMagick / ImageMagick

Упорядкований Dither:

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

Перш ніж скаржитися на те, що я використовую "встановлений алгоритм", будь ласка, прочитайте ChangeLog для GraphicsMagick та ImageMagick за квітень 2003 року, де ви побачите, що я реалізував алгоритм у цих програмах. Також нове поєднання "-гамма .45455" з "-порядкованим-детренгом" є новим.

"-Gamma .45455" дбає про те, щоб зображення було занадто світлим. Параметр "все" потрібен лише для GraphicsMagick.

Існує смуга, тому що в зображенні 4х4 впорядкованого дитресу є лише 17 гравілетів. Зовнішній вигляд обв'язки можна зменшити, використовуючи 8х8 впорядкованих дитрей, які мають 65 рівнів.

Ось оригінальне зображення, упорядкований розподілений вихід 4х4 та 8х8 та вихід з випадковим порогом: введіть тут опис зображення

Я віддаю перевагу впорядкованій версії, але включаю випадкову порогову версію для повноти.

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

"10x90%" означає виводити пікселі нижче 10-відсоткової інтенсивності як чисто-чорні та вище 90 відсотків як чисто-білі, щоб уникнути декількох самотніх плям у цих областях.

Напевно, варто відзначити, що обидва є настільки ефективними, що й пам'ять. Ніякої дифузії не відбувається, тому вони працюють один піксель одночасно, навіть коли пишуть блоки з упорядкованим дитретом, і не потрібно нічого знати про сусідні пікселі. ImageMagick і GraphicsMagick обробляють рядки за один раз, але це не потрібно для цих методів. На моєму старому комп’ютері x86_64 реальні години конверсії впорядкованого дитрету займають менше 0,04 секунди.


31
"Перш ніж скаржитися на те, що я використовую" встановлений алгоритм ", будь ласка, прочитайте ChangeLog для GraphicsMagick та ImageMagick за квітень 2003 року, де ви побачите, що я реалізував алгоритм у цих програмах." +1 для чистої щоки.
Джо З.

22

Прошу вибачення за стиль коду, я поєднав це за допомогою деяких бібліотек, які ми щойно побудували в моєму класі java, і він має поганий випадок копіювання-вставки та магічні номери. Алгоритм вибирає випадкові прямокутники на зображенні та перевіряє, чи середня яскравість більша на пофарбованому зображенні чи вихідному зображенні. Потім він вмикає або вимикає піксель, щоб наблизити яскравість до лінійки, бажано підбираючи пікселі, які більше відрізняються від вихідного зображення. Я думаю, що це робить кращу роботу, виводячи тонкі деталі, як волосся у щеняти, але образ шумніший, оскільки він намагається виявити деталі навіть у місцях, де їх немає.

введіть тут опис зображення

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

Я вважаю, що це детерміновано? Якщо так, то як це швидко?
Οurous

Це випадково і на моєму комп'ютері займає близько 3 секунд.
QuadmasterXLII

2
Хоча, можливо, це не найвигідніший алгоритм, результати є мистецтвом самі по собі.
AJMansfield

4
Мені дуже подобається зовнішній вигляд цього алгоритму! Але я думаю, можливо, це виглядає частково так добре, оскільки воно дає текстуру, приблизно схожу на хутро, а це тварина з хутром. Але я не зовсім впевнений, що це правда. Чи можете ви розмістити ще одне зображення, наприклад, автомобіля?
напівкрединне

1
Я думаю, що це найкраща відповідь, як з точки зору оригінальності алгоритму, так і з точки зору дивовижності результатів. Я також дуже хотів би, щоб він працював і на деяких інших зображеннях.
Натаніел

13

Ghostscript (за невеликої допомоги ImageMagick)

Далеко не мій "власний новий алгоритм", але, вибачте, не втримався.

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 -

введіть тут опис зображення

Звичайно, це працює краще без обмежень "однакового розміру".


2
Це смішно. Мене приголомшує той факт, що ніхто не коментував цього дива у стилі Уорхола.
Андрей Костирка

10

JAVA

Ось моє подання. Знімає зображення у форматі JPG, обчислює піксель на освітленість пікселя (завдяки Бонану в цьому питанні), а потім перевіряє його на випадковій схемі, щоб знати, чи отриманий піксель буде чорним чи білим. Темніші пікселі завжди будуть чорними, а найяскравіші - завжди білі, щоб зберегти деталі зображення.

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

}

Оброблене зображення

Інші приклади:

Оригінал Обробляється

Також працює з повнокольоровими зображеннями:

Кольорове зображення Результат


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 байт :)
Він використовує формат ASCII PGM (P2) без рядка коментарів, як для введення, так і для виводу.

Метод є дуже базовим: він додає квадрати розміром 2 * 2 пікселі, перетворюється на діапазон 0..4, потім використовує відповідний зразок у 4 біти для отримання 2 * 2 чорно-білих пікселів.
Це також означає, що ширина і висота повинні бути рівними.

Зразок:

детермінований щеня

І випадковий алгоритм усього 27 байт:

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

Він використовує той самий формат файлу.

Зразок:

випадкове цуценя

І, нарешті, змішаний підхід: випадкове балування з ухилом до шаблону шахів; 44 байти:

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

Зразок:

змішаний цуценя


2
Перший є порівнянним із програмою Nintendo DSi "Flipnote Studio".
BobTheAwesome

6

Java (1.4+)

Я не впевнений у тому, чи я винайду колесо тут, але думаю, що це може бути унікальним ...

з обмеженими випадковими послідовностями

З обмеженими випадковими послідовностями

Чиста випадкова трахання

Чиста випадкова трахання

введіть тут опис зображення

Зображення міста з відповіді Аверроеса

Алгоритм використовує концепцію локалізованої енергії світності та нормалізації для збереження функцій. Тоді в початковій версії було використано випадкове тремтіння, щоб створити розрізнений погляд на ділянки подібної світності. Однак це було не так візуально привабливо. Для того, щоб протистояти цьому, обмежений набір обмежених випадкових послідовностей відображається на світність вхідних пікселів, і зразки використовуються ітераційно та багаторазово, даючи рефлекторні фони.

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
Дуже хороша. Це безумовно дає інший ефект, ніж інші відповіді поки що.
Геобіт

@Geobits Так, це мене здивувало, наскільки це ефективно. Однак я не впевнений, чи я би назвав це диханням, оскільки він дає досить візуально інший вихід
Moogie

Це виглядає досить унікально.
qwr

5

Пітон

Ідея така: зображення поділяється на n x nплитки. Ми обчислюємо середній колір кожної з цих плиток. Потім ми зіставляємо кольорову гаму 0 - 255в діапазон, 0 - n*nякий дає нам нове значення v. Потім ми забарвлюємо всі пікселі з цієї плитки в чорний колір, а випадково кольорові vпікселі всередині цієї плитки - білі. Це далеко не оптимально, але все ж дає нам впізнавані результати. Залежно від роздільної здатності, вона, як правило, найкраще працює n=2або n=3. У той час як у n=2вас уже можна знайти артефакти із "імітованої глибини кольору", на випадок, якщо n=3вона вже може бути дещо розмитою. Я припускав, що зображення повинні залишатися однакового розміру, але ви, звичайно, також можете скористатися цим методом і просто подвоїти / потріювати розмір створеного зображення, щоб отримати більше деталей.

PS: Я знаю, що я трохи спізнююся на вечірку, я пам’ятаю, що у мене не було ідеї, коли розпочався виклик, але зараз просто ця хвиля мозку =)

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

Результати:

n=2:

введіть тут опис зображення

n=3:

введіть тут опис зображення


3

Будь-який формат файлу, який ви хочете, добре.

Давайте визначимо дуже компактний, теоретичний формат файлу для цього питання, оскільки будь-який із існуючих форматів файлів має занадто великі накладні витрати, щоб написати швидку відповідь.

Нехай перші чотири байти файлу зображення визначають відповідно ширину та висоту зображення в пікселях:

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

з подальшим w * hбайтом значень сірого від 0 до 255:

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

Тоді ми можемо визначити фрагмент коду в Python (145 байт), який буде приймати це зображення і робити:

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

який "вмирає", повертаючи білий або чорний з ймовірністю, рівним значенню сірого цього пікселя.


Застосований на зразковому зображенні, він дає щось подібне:

газований собака

Це не надто красиво, але виглядає дуже схоже, коли його зменшують у попередньому перегляді, і всього за 145 байт Python, я не думаю, що ви можете набагато краще.


Чи можете ви поділитися прикладом? Я вважаю, що це випадкове замирання, і результати не
найясніші

Це насправді випадкове дихання, і я зараз роблю приклад вашої зразкової картини.
Джо З.

2
Я думаю, що це може виграти від контрастування. Я не знаю python, але я припускаю, що random.randint (0,255) набирає випадкове число від 0 до 255. Спробуйте обмежити значення між 55 і 200, що змусить будь-які відтінки поза цим діапазоном бути чисто чорними або білими. З багатьма картинками ви можете отримати гарне, вражаюче зображення, без відмирання, просто простий поріг. (Випадкове + контрастне збільшення дасть проміжне зображення між вашим поточним зображенням та простим порогом.)
Level River St

Я думаю, що випадкове балування повинно називатися Гейгеровим дитренгом (адже це схоже на вихід лічильника Гейгера). Хто погоджується?
Джо З.

1
Це майже саме те, що ImageMagick і GraphicsMagick роблять з опцією "-випадковий поріг", яку я додав разом з "-порядкованим-дріттером" років тому (доданий до моєї відповіді). Знову ж таки, нахил гами допомагає отримати потрібну інтенсивність. Я погоджуюсь із пропозицією «Гейгера дрімучи».
Гленн Рендерс-Персон

3

Кобра

Знімає 24-бітний або 32-розрядний файл PNG / BMP (JPG видає вихід з деякими сірими в ньому). Він також доступний для файлів, що містять колір.

Він використовує оптимізовану швидкість ELA, щоб пофарбувати зображення в 3-бітний колір, який повернеться як чорний / білий, якщо надати тестове зображення.

Я згадав, що це дійсно швидко?

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)

Пес

Дерева


Щоб зменшити повторення, чи планували ви створити тимчасову змінну colта залишити image.setPixel(x,y,col)до самого кінця?
joeytwiddle

3
Що з зображенням дерев?
AJMansfield

Це добре виглядає, і наводить приклад такої роботи з кольорами.
Οurous

2

Java

Код низького рівня, використовуючи PNGJ та додавання шуму плюс основну дифузію. Ця реалізація потребує 8-бітового джерела PNG у градаціях сірого.

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

}

(Додайте цю банку до контуру збирання, якщо ви хочете спробувати).

введіть тут опис зображення

Як бонус: це надзвичайно ефективно у використанні пам’яті (вона зберігає лише три ряди), тому її можна використовувати для величезних зображень.


Нітпік: Я вважаю, що "використовується для величезних зображень" не так важливо (ви коли-небудь бачили PNG з розмаїттям сірого кольору в 8 ГБ?), Але "використовується на наприклад вбудованих пристроях" є набагато більш помітним моментом.
напів зовнішній

Мені це подобається, але він виглядає трохи розмитим по краях, метуючи.
BobTheAwesome

1

Java

Просто простий алгоритм на основі RNG, а також певна логіка роботи з кольоровими зображеннями. Має ймовірність b встановлення будь-якого пікселя на білий, встановлює його чорним кольором в іншому випадку; де b - початкова яскравість пікселя.

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

Ось потенційний результат для образу собаки:

введіть тут опис зображення


Чому б ви не додали пояснення вгорі, а не внизу, де ніхто не збирається її читати? Мені дуже подобається ця ідея =)
flawr
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.