Pemprosesan gambar dengan Java 2D

Pemprosesan gambar adalah seni dan sains memanipulasi gambar digital. Ini berdiri dengan satu kaki dalam matematik dan yang lain dalam estetika, dan merupakan komponen penting dalam sistem komputer grafik. Sekiranya anda pernah bersusah payah membuat gambar anda sendiri untuk halaman Web, anda pasti akan menghargai kepentingan keupayaan manipulasi gambar Photoshop untuk membersihkan imbasan dan membersihkan gambar yang kurang optimum.

Sekiranya anda melakukan kerja pemprosesan gambar dalam JDK 1.0 atau 1.1, anda mungkin ingat bahawa ia agak tidak jelas. Model lama pengeluar data pengguna dan pengguna tidak sukar untuk memproses gambar. Sebelum JDK 1.2, pemprosesan gambar melibatkan MemoryImageSources, PixelGrabbers, dan arcana lain. Java 2D, bagaimanapun, menyediakan model yang lebih bersih dan mudah digunakan.

Bulan ini, kami akan memeriksa algoritma di sebalik beberapa operasi pemprosesan gambar ( ops ) penting dan menunjukkan kepada anda bagaimana ia dapat dilaksanakan menggunakan Java 2D. Kami juga akan menunjukkan kepada anda bagaimana ops ini digunakan untuk mempengaruhi penampilan gambar.

Oleh kerana pemprosesan imej adalah aplikasi mandiri Java 2D yang benar-benar berguna, kami telah membina contoh bulan ini, ImageDicer, agar dapat digunakan kembali untuk aplikasi anda sendiri. Contoh tunggal ini menunjukkan semua teknik pemprosesan gambar yang akan kita bahas di ruangan bulan ini.

Perhatikan bahawa tidak lama sebelum artikel ini diterbitkan, Sun mengeluarkan kit pengembangan Java 1.2 Beta 4. Beta 4 nampaknya memberikan prestasi yang lebih baik untuk operasi pemprosesan gambar contoh kami, tetapi ia juga menambah beberapa bug baru yang melibatkan pemeriksaan ConvolveOps. Masalah-masalah ini mempengaruhi pengesanan tepi dan penajaman contoh yang kita gunakan dalam perbincangan kita.

Kami fikir contoh-contoh ini sangat berharga, jadi daripada menghindarinya sama sekali, kami berkompromi: untuk memastikannya berjalan, kod contoh mencerminkan perubahan Beta 4, tetapi kami telah mengekalkan angka dari pelaksanaan 1.2 Beta 3 sehingga anda dapat melihat operasi berfungsi dengan betul.

Mudah-mudahan, Sun akan mengatasi bug ini sebelum pelepasan Java 1.2 terakhir.

Pemprosesan gambar bukan sains roket

Pemprosesan gambar tidak semestinya sukar. Sebenarnya, konsep asasnya sangat mudah. Bagaimanapun, gambar hanyalah segi empat tepat piksel berwarna. Memproses gambar hanyalah mengira warna baru untuk setiap piksel. Warna baru setiap piksel dapat didasarkan pada warna piksel yang ada, warna piksel sekitarnya, parameter lain, atau kombinasi elemen-elemen ini.

API 2D memperkenalkan model pemprosesan gambar yang mudah untuk membantu pembangun memanipulasi piksel gambar ini. Model ini berdasarkan java.awt.image.BufferedImagekelas, dan operasi pemprosesan gambar seperti konvolusi dan ambang ditunjukkan oleh pelaksanaan java.awt.image.BufferedImageOpantara muka.

Pelaksanaan ops ini agak mudah. Anggaplah, sebagai contoh, bahawa anda sudah mempunyai gambar sumber sebagai BufferedImagepanggilan source. Melakukan operasi yang digambarkan dalam gambar di atas hanya memerlukan beberapa baris kod:

001 pendek [] ambang = pendek baru [256]; 002 untuk (int i = 0; i <256; i ++) ambang 003 [i] = (i <128)? (pendek) 0: (pendek) 255; 004 BufferedImageOp ambangOp = 005 LookupOp baru (ShortLookupTable baru (0, ambang), null); 006 Destinasi BufferedImage = thresholdOp.filter (sumber, null);

Itu semua ada padanya. Sekarang mari kita lihat langkah-langkahnya dengan lebih terperinci:

  1. Buat operasi gambar pilihan anda (baris 004 dan 005). Di sini kami menggunakan LookupOp, yang merupakan salah satu operasi gambar yang termasuk dalam implementasi Java 2D. Seperti operasi gambar lain, ia menggunakan BufferedImageOpantara muka. Kami akan membincangkan lebih lanjut mengenai operasi ini kemudian.

  2. Panggil kaedah operasi filter()dengan gambar sumber (baris 006). Sumber diproses dan gambar tujuan dikembalikan.

Sekiranya anda sudah membuat gambar BufferedImageyang akan menyimpan gambar tujuan, anda dapat meneruskannya sebagai parameter kedua ke filter(). Sekiranya anda lulus null, seperti yang kami lakukan dalam contoh di atas, destinasi baru BufferedImageakan dibuat.

API 2D merangkumi sebilangan kecil operasi imej terbina dalam ini. Kami akan membincangkan tiga dalam lajur ini: konvolusi, jadual pencarian, dan ambang. Sila rujuk dokumentasi Java 2D untuk maklumat mengenai operasi yang masih ada di API 2D (Sumber).

Konvolusi

A kekusutan operasi membolehkan anda untuk menggabungkan warna piksel sumber dan jiran-jirannya untuk menentukan warna piksel dan menarik. Kombinasi ini ditentukan menggunakan kernel, operator linier yang menentukan perkadaran setiap warna piksel sumber yang digunakan untuk menghitung warna piksel tujuan.

Fikirkan kernel sebagai templat yang dilapisi pada gambar untuk melakukan konvolusi pada satu piksel pada satu masa. Oleh kerana setiap piksel berbelit-belit, templat dipindahkan ke piksel berikutnya dalam gambar sumber dan proses konvolusi diulang. Salinan sumber gambar digunakan untuk nilai input untuk konvolusi, dan semua nilai output disimpan ke salinan tujuan gambar. Setelah operasi konvolusi selesai, gambar tujuan dikembalikan.

Pusat kernel dapat dianggap sebagai overlay piksel sumber yang berbelit-belit. Sebagai contoh, operasi konvolusi yang menggunakan kernel berikut tidak berpengaruh pada gambar: setiap piksel tujuan mempunyai warna yang sama dengan piksel sumber yang sesuai.

 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 

Peraturan kardinal untuk membuat kernel adalah bahawa semua elemen harus menambah hingga 1 jika anda ingin mengekalkan kecerahan gambar.

Dalam API 2D, konvolusi diwakili oleh a java.awt.image.ConvolveOp. Anda boleh membina ConvolveOpkernel, yang diwakili oleh contoh java.awt.image.Kernel. Kod berikut membina ConvolveOpkernel yang ditunjukkan di atas.

001 float [] identitiKernel = {002 0.0f, 0.0f, 0.0f, 003 0.0f, 1.0f, 0.0f, 004 0.0f, 0.0f, 0.0f 005}; 006 Identiti BufferedImageOp = 007 ConvolveOp baru (Kernel baru (3, 3, Identiti Kernel));

Operasi konvolusi berguna dalam melakukan beberapa operasi biasa pada gambar, yang akan kita perincikan sebentar lagi. Kernel yang berbeza menghasilkan hasil yang berbeza secara radikal.

Sekarang kami bersedia untuk menggambarkan beberapa kernel pemprosesan gambar dan kesannya. Gambar kami yang tidak diubah suai adalah Lady Agnew dari Lochnaw, yang dilukis oleh John Singer Sargent pada tahun 1892 dan 1893.

Kod berikut menghasilkan kod ConvolveOpyang menggabungkan jumlah yang sama bagi setiap piksel sumber dan tetangganya. Teknik ini menghasilkan kesan kabur.

001 terapung kesembilan = 1.0f / 9.0f; 002 float [] blurKernel = {003 kesembilan, kesembilan, kesembilan, 004 kesembilan, kesembilan, kesembilan, 005 kesembilan, kesembilan, kesembilan 006}; 007 BufferedImageOp blur = ConvolveOp baru (Kernel baru (3, 3, blurKernel));

Kernel konvolusi yang lain menekankan bahagian tepi gambar. Operasi ini biasanya disebut pengesanan tepi. Tidak seperti kernel lain yang disajikan di sini, pekali kernel ini tidak bertambah hingga 1.

001 float [] edgeKernel = {002 0.0f, -1.0f, 0.0f, 003 -1.0f, 4.0f, -1.0f, 004 0.0f, -1.0f, 0.0f 005}; 006 BufferedImageOp edge = ConvolveOp baru (Kernel baru (3, 3, edgeKernel));

Anda dapat melihat apa yang dilakukan oleh kernel ini dengan melihat pekali dalam kernel (baris 002-004). Fikirkan sejenak bagaimana kernel pengesanan tepi digunakan untuk beroperasi di kawasan yang sepenuhnya satu warna. Setiap piksel akan berakhir tanpa warna (hitam) kerana warna piksel sekitarnya membatalkan warna piksel sumber. Piksel terang yang dikelilingi oleh piksel gelap akan tetap terang.

Perhatikan betapa gelapnya gambar yang diproses dibandingkan dengan yang asli. Ini berlaku kerana unsur-unsur kernel pengesanan tepi tidak menambah hingga 1.

A simple variation on edge detection is the sharpening kernel. In this case, the source image is added into an edge detection kernel as follows:

 0.0 -1.0 0.0 0.0 0.0 0.0 0.0 -1.0 0.0 -1.0 4.0 -1.0 + 0.0 1.0 0.0 = -1.0 5.0 -1.0 0.0 -1.0 0.0 0.0 0.0 0.0 0.0 -1.0 0.0 

The sharpening kernel is actually only one possible kernel that sharpens images.

The choice of a 3 x 3 kernel is somewhat arbitrary. You can define kernels of any size, and presumably they don't even have to be square. In JDK 1.2 Beta 3 and 4, however, a non-square kernel produced an application crash, and a 5 x 5 kernel chewed up the image data in a most peculiar way. Unless you have a compelling reason to stray from 3 x 3 kernels, we don't recommend it.

You may also be wondering what happens at the edge of the image. As you know, the convolution operation takes a source pixel's neighbors into account, but source pixels at the edges of the image don't have neighbors on one side. The ConvolveOp class includes constants that specify what the behavior should be at the edges. The EDGE_ZERO_FILL constant specifies that the edges of the destination image are set to 0. The EDGE_NO_OP constant specifies that source pixels along the edge of the image are copied to the destination without being modified. If you don't specify an edge behavior when constructing a ConvolveOp, EDGE_ZERO_FILL is used.

The following example shows how you could create a sharpening operator that uses the EDGE_NO_OP rule (NO_OP is passed as a ConvolveOp parameter in line 008):

001 float[] sharpKernel = { 002 0.0f, -1.0f, 0.0f, 003 -1.0f, 5.0f, -1.0f, 004 0.0f, -1.0f, 0.0f 005 }; 006 BufferedImageOp sharpen = new ConvolveOp( 007 new Kernel(3, 3, sharpKernel), 008 ConvolveOp.EDGE_NO_OP, null); 

Lookup tables

Another versatile image operation involves using a lookup table. For this operation, source pixel colors are translated into destination pixels colors through the use of a table. A color, remember, is composed of red, green, and blue components. Each component has a value from 0 to 255. Three tables with 256 entries are sufficient to translate any source color to a destination color.

The java.awt.image.LookupOp and java.awt.image.LookupTable classes encapsulate this operation. You can define separate tables for each color component, or use one table for all three. Let's look at a simple example that inverts the colors of every component. All we need to do is create an array that represents the table (lines 001-003). Then we create a LookupTable from the array and a LookupOp from the LookupTable (lines 004-005).

001 short[] invert = new short[256]; 002 for (int i = 0; i < 256; i++) 003 invert[i] = (short)(255 - i); 004 BufferedImageOp invertOp = new LookupOp( 005 new ShortLookupTable(0, invert), null); 

LookupTable has two subclasses, ByteLookupTable and ShortLookupTable, that encapsulate byte and short arrays. If you create a LookupTable that doesn't have an entry for any input value, an exception will be thrown.

This operation creates an effect that looks like a color negative in conventional film. Also note that applying this operation twice will restore the original image; you're basically taking a negative of the negative.

What if you only wanted to affect one of the color components? Easy. You construct a LookupTable with separate tables for each of the red, green, and blue components. The following example shows how to create a LookupOp that only inverts the blue component of the color. As with the previous inversion operator, applying this operator twice restores the original image.

001 short[] invert = new short[256]; 002 short[] straight = new short[256]; 003 for (int i = 0; i < 256; i++) { 004 invert[i] = (short)(255 - i); 005 straight[i] = (short)i; 006 } 007 short[][] blueInvert = new short[][] { straight, straight, invert }; 008 BufferedImageOp blueInvertOp = 009 new LookupOp(new ShortLookupTable(0, blueInvert), null); 

Posterizing is another nice effect you can apply using a LookupOp. Posterizing involves reducing the number of colors used to display an image.

A LookupOp can achieve this effect by using a table that maps input values to a small set of output values. The following example shows how input values can be mapped to eight specific values.

001 pendek [] posterize = pendek baru [256]; 002 untuk (int i = 0; i <256; i ++) 003 posterize [i] = (pendek) (i - (i% 32)); 004 BufferedImageOp posterizeOp = 005 LookupOp baru (ShortLookupTable baru (0, posterize), null);

Ambang

Operasi gambar terakhir yang akan kita kaji adalah ambang. Thresholding membuat perubahan warna melintasi "batas" atau ambang yang ditentukan oleh programmer, lebih jelas (serupa dengan bagaimana garis kontur pada peta menjadikan batas ketinggian lebih jelas). Teknik ini menggunakan nilai ambang yang ditentukan, nilai minimum, dan nilai maksimum untuk mengawal nilai komponen warna untuk setiap piksel gambar. Nilai warna di bawah ambang diberikan nilai minimum. Nilai di atas ambang diberikan nilai maksimum.