Hình ảnh thang độ xám


23

Kết hợp hình ảnh thang độ xám thành đen trắng thuần túy với thuật toán của riêng bạn.

Hướng dẫn: Bạn phải đưa ra thuật toán mới của riêng bạn. Bạn không thể sử dụng các thuật toán có sẵn (ví dụ Floyd-Steinburg) nhưng bạn có thể sử dụng kỹ thuật chung. Chương trình của bạn phải có khả năng đọc một hình ảnh và tạo ra một hình ảnh có cùng kích thước. Đây là một cuộc thi phổ biến, vì vậy ai tạo ra sản phẩm tốt nhất (gần nhất với bản gốc) và sáng tạo nhất (được xác định bằng phiếu bầu) sẽ giành chiến thắng. Tiền thưởng nếu mã ngắn, mặc dù điều này là không cần thiết.

Bạn có thể sử dụng bất kỳ hình ảnh thang độ xám nào bạn muốn làm đầu vào, nó phải lớn hơn 300x300. Bất kỳ định dạng tập tin là tốt.

Ví dụ đầu vào:

cún yêu

Ví dụ đầu ra:

hoà sắc

Đây là một công việc khá tốt, nhưng vẫn có những đường và mẫu có thể nhìn thấy.


4
+1 cho một thử thách thú vị, nhưng tôi nghĩ rằng điều này sẽ tốt hơn nhiều khi chơi [code-golf] (với thông số kỹ thuật) hoặc một số tiêu chí hoàn toàn khách quan khác.
Doorknob

2
Vấn đề với việc sử dụng kích thước mã, tốc độ và bộ nhớ là bạn cần một ngưỡng khách quan để biết kết quả phải dễ nhận biết như thế nào để câu trả lời có giá trị, điều này cũng khá bất khả thi. Cuộc thi phổ biến có ý nghĩa, nhưng không có bất kỳ hạn chế nào về mã, không có động cơ để mọi người nghĩ ra khỏi hộp. Tôi thà đưa ra một câu trả lời thông minh hơn là đưa ra kết quả tốt nhất vì nó chỉ thực hiện một thuật toán hiện có. Nhưng bạn hiện đang khuyến khích sau này.
Martin Ender

3
Ranh giới giữa một thuật toán và kỹ thuật của nó quá mỏng để xác định bên nào đó rơi xuống.
Peter Taylor

2
Tôi nghĩ rằng sẽ dễ dàng hơn nhiều để so sánh kết quả nếu tất cả đều cho thấy kết quả từ cùng một hình ảnh.
joeytwiddle

3
Bạn có thể thêm nguồn của hình ảnh? (Tôi không nghĩ ai đó sẽ tức giận khi thấy hình ảnh của anh ấy / cô ấy ở đây, nhưng thật công bằng khi trích dẫn nguồn này)
AL

Câu trả lời:


16

Pháo đài

Được rồi, tôi đang sử dụng một định dạng hình ảnh tối nghĩa được gọi là FITS được sử dụng cho thiên văn học. Điều này có nghĩa là có một thư viện Fortran để đọc và viết những hình ảnh như vậy. Ngoài ra, ImageMagick và Gimp có thể đọc / ghi hình ảnh FITS.

Thuật toán tôi sử dụng dựa trên phối màu "Sierra Lite", nhưng với hai cải tiến:
a) Tôi giảm lỗi lan truyền theo hệ số 4/5.
b) Tôi giới thiệu một biến thể ngẫu nhiên trong ma trận khuếch tán trong khi giữ cho tổng của nó không đổi.
Cùng nhau những điều này gần như hoàn toàn tối ưu hóa các mẫu được thấy trong ví dụ OP.

Giả sử bạn đã cài đặt thư viện CFITSIO, biên dịch với

gfortran -lcuitsio dither.f90

Tên tệp được mã hóa cứng (không thể bị làm phiền để sửa lỗi này).

Mã số:

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

Đầu ra ví dụ cho hình ảnh con chó con trong bài
Hình ảnh hoà sắc của con chó con
OP: Đầu ra ví dụ OP:
Hình ảnh hoà sắc của OP


Điều này có vẻ thực sự tốt, có thể là vô địch về chất lượng
aditsu

Cảm ơn! Tôi không biết rằng nó là vô địch, nhưng sẽ rất khó (rất chủ quan) để đánh giá điều này so với các thuật toán tốt khác.
bán ngoài

1
Tôi biết mã golf của mình bằng cách lạm dụng khả năng tương thích ngược, nhưng thực sự có vẻ như bạn lạm dụng nó là tiêu chuẩn. Mã này thực sự làm tôi khóc.
Kyle Kanos

@KyleKanos Tôi luôn vui khi mã của mình khiến ai đó khóc: p Về chủ đề, điều gì đặc biệt kinh khủng ở đây? Vâng, tôi đã có thể sử dụng "không ẩn", nhưng niềm vui trong đó là ở đâu? Tôi sử dụng nó để mã hóa nghiêm túc trong công việc, nhưng không phải để chơi gôn. Và tôi chắc chắn đồng ý rằng API thư viện CFITSIO hoàn toàn khủng khiếp (ftppre () xuất ra hình ảnh FITS với độ chính xác thực duy nhất, ftpprj () đưa ra hình ảnh với độ chính xác gấp đôi, v.v.) nhưng đó là khả năng tương thích ngược F77 cho bạn.
bán ngoài

1
Được rồi, vì vậy hầu hết những người đó chỉ là tôi cẩu thả. Tôi đã cải thiện nó. Những lời chỉ trích mang tính xây dựng luôn được đánh giá cao :)
bán ngoài

34

Đồ họaMagick / ImageMagick

Thứ tự hoà sắc:

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

Trước khi phàn nàn về việc tôi sử dụng "thuật toán đã thiết lập", vui lòng đọc ChangeLog cho GraphicsMagick và ImageMagick cho tháng 4 năm 2003, nơi bạn sẽ thấy rằng tôi đã triển khai thuật toán trong các ứng dụng đó. Ngoài ra, sự kết hợp của "-gamma .45455" với "-ordered-dither" là mới.

"-Gamma .45455" cho rằng hình ảnh quá sáng. Tham số "tất cả" chỉ cần thiết với GraphicsMagick.

Có dải vì chỉ có 17 màu xám trong hình ảnh được sắp xếp theo thứ tự 4 x 4. Sự xuất hiện của dải có thể được giảm bằng cách sử dụng một bộ hòa âm theo thứ tự 8x8 có 65 cấp độ.

Dưới đây là hình ảnh gốc, đầu ra được phối màu theo thứ tự 4 x 4 và 8 x 8 và đầu ra ngưỡng ngẫu nhiên: nhập mô tả hình ảnh ở đây

Tôi thích phiên bản hòa âm theo thứ tự, nhưng bao gồm cả phiên bản ngưỡng ngẫu nhiên để hoàn thiện.

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

"10x90%" có nghĩa là hiển thị các pixel cường độ dưới 10 phần trăm dưới dạng màu đen thuần khiết và trên 90 phần trăm là màu trắng tinh khiết, để tránh có một vài đốm cô đơn trong các khu vực đó.

Có lẽ đáng lưu ý rằng cả hai đều có hiệu quả bộ nhớ như họ có thể có. Không có bất kỳ sự khuếch tán nào, vì vậy chúng hoạt động một pixel mỗi lần, ngay cả khi viết các khối hoà sắc theo thứ tự và không cần biết gì về các pixel lân cận. ImageMagick và GraphicsMagick xử lý một hàng tại một thời điểm, nhưng không cần thiết cho các phương thức này. Các chuyển đổi hòa sắc theo thứ tự mất ít hơn 0,04 giây thời gian thực trên máy tính x86_64 cũ của tôi.


31
"Trước khi phàn nàn về việc tôi sử dụng" thuật toán đã thiết lập ", vui lòng đọc ChangeLog cho GraphicsMagick và ImageMagick cho tháng 4 năm 2003, nơi bạn sẽ thấy rằng tôi đã triển khai thuật toán trong các ứng dụng đó." +1 cho má tuyệt đối.
Joe Z.

22

Tôi xin lỗi về kiểu mã, tôi đã kết hợp nó với nhau bằng cách sử dụng một số thư viện chúng tôi vừa xây dựng trong lớp java của mình và nó có một trường hợp xấu về sao chép và số ma thuật. Thuật toán chọn các hình chữ nhật ngẫu nhiên trong ảnh và kiểm tra xem độ sáng trung bình có lớn hơn trong ảnh được phối màu hay ảnh gốc không. Sau đó, nó bật hoặc tắt một pixel để mang lại độ sáng gần hơn, tốt nhất là chọn các pixel khác với ảnh gốc. Tôi nghĩ rằng đó là một công việc tốt hơn để đưa ra các chi tiết mỏng như lông của con chó con, nhưng hình ảnh thì ồn ào hơn vì nó cố gắng đưa ra các chi tiết ngay cả ở những khu vực không có.

nhập mô tả hình ảnh ở đây

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

Tôi cho rằng đây là quyết định? Nếu vậy, nó nhanh như thế nào?
Οurous

Đó là ngẫu nhiên và mất khoảng 3 giây trên máy tính của tôi.
QuadmasterXLII

2
Trong khi có lẽ không phải là thuật toán có độ trung thực lớn nhất, kết quả là tất cả đều là nghệ thuật.
AJMansfield

4
Tôi thực sự thích giao diện của thuật toán này! Nhưng tôi nghĩ có lẽ nó trông rất tốt một phần vì nó tạo ra một kết cấu gần giống với lông thú, và đây là một con vật có lông. Nhưng tôi không hoàn toàn chắc chắn điều này là đúng. Bạn có thể đăng một hình ảnh khác của ví dụ như một chiếc xe hơi?
bán ngoài

1
Tôi nghĩ rằng đây là câu trả lời tốt nhất, cả về tính nguyên bản của thuật toán và về sự tuyệt vời của kết quả. Tôi cũng thực sự muốn thấy nó chạy trên một số hình ảnh khác.
Nathaniel

13

Ghostscript (với sự trợ giúp nhỏ của ImageMagick)

Khác xa với 'thuật toán mới của riêng tôi', nhưng, xin lỗi, không thể cưỡng lại nó.

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 -

nhập mô tả hình ảnh ở đây

Tất nhiên nó hoạt động tốt hơn mà không cần hạn chế 'cùng kích cỡ'.


2
Đây là vui nhộn. Tôi choáng váng vì thực tế rằng không ai bình luận về tuyệt tác theo phong cách Warhol này.
Andreï Kostyrka

10

JAVA

Đây là đệ trình của tôi. Chụp ảnh JPG, tính toán độ sáng pixel trên mỗi pixel (nhờ Bonan trong câu hỏi SO này ) và sau đó kiểm tra nó theo một mẫu ngẫu nhiên để biết pixel kết quả sẽ có màu đen hay trắng. Các pixel tối hơn sẽ luôn có màu đen và các pixel sáng nhất sẽ luôn có màu trắng để giữ chi tiết hình ảnh.

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

}

Hình ảnh được xử lý

Những ví dụ khác:

Nguyên Xử lý

Cũng hoạt động với hình ảnh đầy đủ màu sắc:

Hình ảnh màu Kết quả


9

Camam

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

95 byte :)
Nó sử dụng định dạng ASCII PGM (P2) không có dòng nhận xét, cho cả đầu vào và đầu ra.

Phương pháp này rất cơ bản: nó cộng các ô vuông 2 * 2 pixel, chuyển đổi thành phạm vi 0..4, sau đó sử dụng mẫu 4 bit tương ứng để tạo các pixel đen trắng 2 * 2.
Điều đó cũng có nghĩa là chiều rộng và chiều cao phải đồng đều.

Mẫu vật:

cún con quyết đoán

Và một thuật toán ngẫu nhiên chỉ trong 27 byte:

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

Nó sử dụng định dạng tập tin tương tự.

Mẫu vật:

cún con ngẫu nhiên

Và cuối cùng là một cách tiếp cận hỗn hợp: phối màu ngẫu nhiên với sự thiên vị đối với một mẫu bàn cờ; 44 byte:

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

Mẫu vật:

cún con


2
Cái đầu tiên có thể so sánh với ứng dụng "Flipnote Studio" của Nintendo DS.
BobTheAwgie

6

Java (1.4+)

Tôi không chắc liệu tôi có phát minh lại bánh xe ở đây không nhưng tôi nghĩ nó có thể là duy nhất ...

với trình tự ngẫu nhiên hạn chế

Với trình tự ngẫu nhiên hạn chế

Phối màu ngẫu nhiên thuần túy

Phối màu ngẫu nhiên thuần túy

nhập mô tả hình ảnh ở đây

Hình ảnh thành phố từ câu trả lời của Averroes

Thuật toán sử dụng khái niệm năng lượng độ sáng cục bộ và chuẩn hóa để giữ lại các tính năng. Phiên bản ban đầu sau đó sử dụng một jitter ngẫu nhiên để tạo ra một cái nhìn hoà sắc trên các khu vực có độ sáng tương tự. Tuy nhiên, nó không phải là hấp dẫn trực quan. Để chống lại điều này, một tập hợp các chuỗi ngẫu nhiên giới hạn có giới hạn được ánh xạ tới độ chói của pixel đầu vào thô và các mẫu được sử dụng lặp đi lặp lại và tạo ra các nền nhìn được phối màu.

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
Rất đẹp. Nó chắc chắn mang lại một hiệu ứng khác so với các câu trả lời khác cho đến nay.
Geobits 18/2/2016

@Geobits Vâng, nó làm tôi ngạc nhiên về hiệu quả của nó. Tuy nhiên tôi không chắc liệu tôi có gọi nó là phối màu hay không vì nó tạo ra đầu ra khá khác biệt
Moogie 18/2/2016

Điều đó trông khá độc đáo.
qwr

5

Con trăn

Ý tưởng là như sau: Hình ảnh được chia thành các n x nô. Chúng tôi tính toán màu trung bình của mỗi viên gạch. Sau đó, chúng tôi ánh xạ phạm vi màu 0 - 255tới phạm vi 0 - n*nmang lại cho chúng tôi một giá trị mới v. Sau đó, chúng tôi tô màu tất cả các pixel từ ô màu đen đó và các vpixel màu ngẫu nhiên trong ô màu trắng đó. Nó là xa tối ưu nhưng vẫn cho chúng ta kết quả dễ nhận biết. Tùy thuộc vào độ phân giải, nó hoạt động thường tốt nhất tại n=2hoặc n=3. Mặc dù trong n=2bạn đã có thể tìm thấy đồ tạo tác từ độ sâu màu mô phỏng, trong trường hợp n=3nó có thể bị mờ đi đôi chút. Tôi giả định rằng các hình ảnh nên giữ nguyên kích thước, nhưng tất nhiên bạn cũng có thể sử dụng phương pháp này và chỉ cần tăng gấp đôi / gấp ba kích thước của hình ảnh được tạo để có thêm chi tiết.

Tái bút: Tôi biết rằng tôi đến bữa tiệc muộn một chút, tôi nhớ rằng tôi không có bất kỳ ý tưởng nào khi thử thách bắt đầu nhưng bây giờ chỉ có sóng não này =)

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

Các kết quả:

n=2:

nhập mô tả hình ảnh ở đây

n=3:

nhập mô tả hình ảnh ở đây


3

Bất kỳ định dạng tập tin bạn muốn là tốt.

Chúng ta hãy định nghĩa một định dạng tệp lý thuyết rất nhỏ gọn cho câu hỏi này vì bất kỳ định dạng tệp hiện có nào có quá nhiều chi phí để viết câu trả lời nhanh cho.

Để bốn byte đầu tiên của tệp hình ảnh xác định chiều rộng và chiều cao của hình ảnh theo pixel, tương ứng:

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

theo sau là w * hbyte của các giá trị thang độ xám từ 0 đến 255:

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

Sau đó, chúng ta có thể định nghĩa một đoạn mã bằng Python (145 byte) sẽ lấy hình ảnh này và thực hiện:

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

mà "phối màu" bằng cách trả về màu trắng hoặc đen với xác suất bằng giá trị thang độ xám của pixel đó.


Áp dụng trên hình ảnh mẫu, nó cung cấp một cái gì đó như thế này:

chó hoà sắc

Nó không quá đẹp, nhưng nó trông rất giống nhau khi được thu nhỏ trong bản xem trước và chỉ với 145 byte Python, tôi không nghĩ bạn có thể trở nên tốt hơn nhiều.


Bạn có thể chia sẻ một ví dụ? Tôi tin rằng đây là phối màu ngẫu nhiên, và kết quả không phải là ... ảnh đại diện đẹp nhất mặc dù
qwr

Đây thực sự là phối màu ngẫu nhiên, và tôi đang làm một ví dụ về hình ảnh mẫu của bạn tại thời điểm này.
Joe Z.

2
Tôi nghĩ rằng nó có thể được hưởng lợi từ một sự tăng cường tương phản. Tôi không biết python, nhưng tôi giả sử Random.randint (0,255) đang chọn một số ngẫu nhiên trong khoảng từ 0 đến 255. Hãy thử giới hạn trong khoảng từ 55 đến 200, điều này sẽ buộc bất kỳ sắc thái nào bên ngoài phạm vi đó phải là màu đen hoặc trắng thuần túy. Với nhiều hình ảnh, bạn có thể có được một hình ảnh đẹp, nổi bật mà không bị hoà sắc, chỉ là một ngưỡng đơn giản. (Tăng ngẫu nhiên + độ tương phản sẽ cung cấp hình ảnh trung gian giữa hình ảnh hiện tại của bạn và ngưỡng đơn giản.)
Level River St

Tôi nghĩ rằng phối màu ngẫu nhiên nên được gọi là phối màu Geiger (vì nó trông giống như đầu ra của bộ đếm Geiger). Ai đồng ý?
Joe Z.

1
Đó gần như chính xác là những gì ImageMagick và GraphicsMagick làm với tùy chọn "-random-ngưỡng" mà tôi đã thêm cùng với "-ordered-dither" năm trước (thêm vào câu trả lời của tôi). Một lần nữa, va chạm gamma giúp có được cường độ phù hợp. Tôi đồng ý với đề xuất "phối màu Geiger".
Glenn Randers-Pehrson

3

Rắn hổ mang

Tạo tệp PNG / BMP 24 bit hoặc 32 bit (JPG tạo đầu ra với một số màu xám trong đó). Nó cũng có thể mở rộng cho các tập tin có chứa màu.

Nó sử dụng ELA được tối ưu hóa tốc độ để hòa trộn hình ảnh thành màu 3 bit, sẽ trả về màu đen / trắng khi đưa ra hình ảnh thử nghiệm của bạn.

Tôi đã đề cập rằng nó thực sự nhanh?

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)

Chó

Cây


Để giảm sự lặp lại, bạn đã xem xét việc tạo một biến tạm thời colvà để lại image.setPixel(x,y,col)cho đến khi kết thúc chưa?
joeytwiddle

3
Những gì với hình ảnh cây?
AJMansfield

Nó trông đẹp, và cung cấp một ví dụ về điều này làm việc với màu sắc là tốt.
Οurous

2

Java

Mã mức thấp, sử dụng PNGJ và bổ sung nhiễu cộng với khuếch tán cơ bản. Việc thực hiện này đòi hỏi nguồn PNG 8 bit thang độ xám.

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

}

(Thêm bình này vào đường dẫn xây dựng của bạn nếu bạn muốn thử nó).

nhập mô tả hình ảnh ở đây

Như một phần thưởng: điều này cực kỳ hiệu quả trong việc sử dụng bộ nhớ (nó chỉ lưu trữ ba hàng) vì vậy nó có thể được sử dụng cho các hình ảnh lớn.


Nitpick: Tôi sẽ nghĩ rằng "được sử dụng cho hình ảnh khổng lồ" không quá quan trọng (bạn đã bao giờ thấy một PNG 8 GB màu xám chưa?), Nhưng "được sử dụng trên các thiết bị nhúng" là một điểm nổi bật hơn nhiều.
bán ngoài

Tôi thích nó nhưng có vẻ hơi mờ xung quanh các cạnh, methinks.
BobTheAwgie

1

Java

Chỉ là một thuật toán dựa trên RNG đơn giản, cộng với một số logic để xử lý hình ảnh màu. Có xác suất b đặt bất kỳ pixel đã cho nào thành màu trắng, đặt nó thành màu đen khác; Trong đó b là độ sáng ban đầu của pixel đó.

import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Random;
import java.util.Scanner;

import javax.imageio.ImageIO;


public class Ditherizer {
    public static void main(String[]a) throws IOException{
        Scanner input=new Scanner(System.in);
        Random rand=new Random();
        BufferedImage img=ImageIO.read(new File(input.nextLine()));
        for(int x=0;x<img.getWidth();x++){
            for(int y=0;y<img.getHeight();y++){
                Color c=new Color(img.getRGB(x, y));
                int r=c.getRed(),g=c.getGreen(),b=c.getBlue();
                double bright=(r==g&&g==b)?r:Math.sqrt(0.299*r*r+0.587*g*g+0.114*b*b);
                img.setRGB(x,y,(rand.nextInt(256)>bright?Color.BLACK:Color.WHITE).getRGB());    
            }
        }
        ImageIO.write(img,"jpg",new File(input.nextLine()));
        input.close();
    }
}

Đây là một kết quả tiềm năng cho hình ảnh con chó:

nhập mô tả hình ảnh ở đây


Tại sao bạn không thêm lời giải thích lên trên thay vì phía dưới nơi không ai sẽ đọc nó? Tôi thực sự thích ý tưởng đó =)
flawr
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.