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


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.