Сглаживание изображения в оттенках серого


23

Разведите серое изображение в чисто черно-белое с помощью собственного алгоритма.

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

Вы можете использовать любое изображение в градациях серого в качестве входных данных, оно должно быть больше, чем 300x300. Любой формат файла в порядке.

Пример ввода:

щенок

Пример вывода:

колебались

Это довольно хорошая работа, но все еще видны линии и узоры.


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

2
Проблема с размером кода, скоростью и использованием памяти заключается в том, что вам нужен объективный порог того, насколько узнаваемым должен быть результат, чтобы ответ был действительным, что также совершенно невозможно. Конкурс популярности имеет смысл, но без каких-либо ограничений в коде у людей нет стимула мыслить нестандартно. Я предпочел бы придумать умный ответ, чем тот, который дает лучший результат, потому что он просто реализовал существующий алгоритм. Но вы в настоящее время стимулируете последнее.
— Мартин Эндер

3
Граница между алгоритмом и его техникой слишком тонкая, чтобы определить, на какую сторону что-то падает.
— Питер Тейлор

2
Я думаю, что было бы намного проще сравнить результаты, если бы все они показали результаты с одного изображения.
— Joeytwiddle

3
Можете ли вы добавить источник изображения? (Я не думаю, что кто-то рассердится, увидев его / ее изображение здесь, но было бы справедливо привести источник)
— AL

Ответы:


16

Фортран

Хорошо, я использую малоизвестный формат изображения под названием FITS, который используется для астрономии. Это означает, что есть библиотека Фортрана для чтения и записи таких изображений. Кроме того, ImageMagick и Gimp могут читать и записывать изображения FITS.

Алгоритм, который я использую, основан на дизеринге «Sierra Lite», но с двумя улучшениями:
a) Я уменьшаю распространенную ошибку в 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

Пример вывода изображения щенка в постах OPs:
Взорванная картинка щенка
Пример вывода OPs:
ОП чутка картина щенка


Это выглядит очень хорошо, может быть непревзойденным по качеству
— aditsu

Благодарность! Я не знаю, что это непобедимо, но будет трудно (очень субъективно) судить об этом по сравнению с другими хорошими алгоритмами.
— Полу-посторонний

1
Я знаю, что играю в гольф, злоупотребляя обратной совместимостью, но на самом деле похоже, что вы злоупотребляете им как стандартом. Этот код на самом деле заставляет меня плакать.
— Кайл Канос

@KyleKanos Я всегда счастлив, когда мой код заставляет кого-то плакать: p По теме, что конкретно здесь ужасного? Да, я мог бы использовать «неявный никто», но где в этом удовольствие? Я использую это для серьезного кодирования на работе, но не для игры в гольф. И я определенно согласен с тем, что API библиотеки CFITSIO совершенно ужасен (ftppre () выводит изображение FITS с одинарной реальной точностью, ftpprj () выводит изображение с двойной целочисленной точностью и т. Д.), Но это обратная совместимость с F77 для вас.
— Половинное

1
Хорошо, так что большинство из них были просто я неаккуратными. Я улучшил это. Конструктивная критика всегда ценится :)
— полу-посторонний

34

GraphicsMagick / ImageMagick

Заказал дизеринг:

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

Прежде чем жаловаться на то, что я использую «установленный алгоритм», прочитайте ChangeLog для GraphicsMagick и ImageMagick за апрель 2003 года, где вы увидите, что я реализовал алгоритм в этих приложениях. Кроме того, сочетание "-gamma .45455" с "-ordered-dither" является новым.

«-Gamma .45455» заботится о том, чтобы изображение было слишком светлым. Параметр "all" необходим только для GraphicsMagick.

Существует полосатость, потому что в изображении с упорядоченным сглаживанием 4х4 только 17 уровней. Появление полос может быть уменьшено с помощью упорядоченного сглаживания 8x8, который имеет 65 уровней.

Вот исходное изображение, упорядоченный выходной сигнал 4x4 и 8x8 и случайный порог: введите описание изображения здесь

Я предпочитаю версию с заказанным дизерингом, но для полноты включаю версию с произвольным порогом.

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

ДЖАВА

Вот мое представление. Снимает изображение в формате JPG, вычисляет яркость пикселей на пиксель (спасибо Бонану в этом вопросе SO) и затем сравнивает его со случайным шаблоном, чтобы узнать, будет ли полученный пиксель черным или белым. Самые темные пиксели всегда будут черными, а самые яркие пиксели будут всегда белыми, чтобы сохранить детали изображения.

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

@ 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 я не думаю, что вы можете стать намного лучше.


Можете поделиться примером? Я считаю, что это случайное размывание, и результаты не самые чистые ... хорошая картинка профиля
— qwr

Это действительно случайное сглаживание, и сейчас я привожу пример вашего образца изображения.
— Джо З.

2
Я думаю, что это может выиграть от повышения контрастности. Я не знаю python, но я предполагаю, что random.randint (0,255) выбирает случайное число от 0 до 255. Попробуйте ограничиться, скажем, от 55 до 200, что приведет к тому, что любые оттенки за пределами этого диапазона будут чисто черными или белыми. Со многими фотографиями вы можете получить хорошее, яркое изображение без сглаживания, просто простой порог. (Случайное + усиление контраста даст промежуточное изображение между вашим текущим изображением и простым порогом.)
— Level River St

Я думаю, что случайное дизеринг следует называть дизерингом Гейгера (потому что он выглядит как выход счетчика Гейгера). Кто согласен?
— Джо З.

1
Это почти точно, что ImageMagick и GraphicsMagick делают с опцией "-random-threshold", которую я добавил вместе с "-ordered-dither" несколько лет назад (добавлено в мой ответ). Опять же, столкновение с гаммой помогает получить правильную интенсивность. Я согласен с предложением "дизеринга Гейгера".
— Гленн Рандерс-Персон

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

Джава

Код низкого уровня с использованием 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]);
    }

}

(Добавить эту банку файл в путь сборки, если хотите попробовать).

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

В качестве бонуса: это чрезвычайно эффективно при использовании памяти (он хранит только три строки), поэтому его можно использовать для больших изображений.


Nitpick: Я думаю, что «использовать для больших изображений» не так важно (вы когда-нибудь видели PNG в градациях серого> 8 ГБ?), Но «использование, например, на встроенных устройствах» - гораздо более важный момент.
— Половинное

Мне нравится это, но это выглядит немного размытым по краям.
— BobTheAwesome

1

Джава

Просто простой алгоритм на основе 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.