Java 2D를 사용한 이미지 처리

이미지 처리는 디지털 이미지를 조작하는 기술이자 과학입니다. 한 발은 수학에서, 다른 발은 미학에서 확고하게 서 있으며 그래픽 컴퓨터 시스템의 중요한 구성 요소입니다. 웹 페이지 용 이미지를 직접 만드는 데 신경을 쓴 적이 있다면 스캔을 정리하고 최적이 아닌 이미지를 정리하는 Photoshop의 이미지 조작 기능의 중요성을 인식 할 것입니다.

JDK 1.0 또는 1.1에서 이미지 처리 작업을 수행 한 경우 약간 둔감하다는 것을 기억할 것입니다. 이미지 데이터 생산자 및 소비자의 이전 모델은 이미지 처리를 다루기 어렵습니다. JDK 1.2 이전에는 이미지 처리에 MemoryImageSources, PixelGrabbers 및 기타 그러한 알카 나가 포함되었습니다. 그러나 Java 2D는 더 깨끗하고 사용하기 쉬운 모델을 제공합니다.

이번 달에는 몇 가지 중요한 이미지 처리 작업 ( ops ) 이면의 알고리즘을 검토하고 Java 2D를 사용하여 구현할 수있는 방법을 보여줍니다. 또한 이러한 작업이 이미지 모양에 영향을 미치는 데 사용되는 방법을 보여줍니다.

이미지 처리는 Java 2D의 진정으로 유용한 독립형 응용 프로그램이기 때문에 이번 달의 예제 인 ImageDicer를 자체 응용 프로그램에서 최대한 재사용 할 수 있도록 만들었습니다. 이 단일 예제는 이번 달 칼럼에서 다룰 모든 이미지 처리 기술을 보여줍니다.

이 기사가 출판되기 직전에 Sun은 Java 1.2 Beta 4 개발 키트를 출시했습니다. 베타 4는 예제 이미지 처리 작업에 더 나은 성능을 제공하는 것 같지만 ConvolveOps의 경계 검사와 관련된 몇 가지 새로운 버그도 추가합니다 . 이러한 문제는 토론에서 사용하는 가장자리 감지 및 선명 화 예제에 영향을줍니다.

우리는 이러한 예제가 가치가 있다고 생각하므로 모두 생략하는 것이 아니라 손상되었습니다. 실행을 보장하기 위해 예제 코드는 베타 4 변경 사항을 반영하지만 1.2 베타 3 실행의 수치를 유지하여 작업을 볼 수 있습니다. 올바르게 작동합니다.

바라건대, Sun은 최종 Java 1.2 릴리스 이전에 이러한 버그를 해결할 것입니다.

이미지 처리는 로켓 과학이 아닙니다

이미지 처리가 어려울 필요는 없습니다. 사실 기본 개념은 정말 간단합니다. 결국 이미지는 컬러 픽셀의 사각형 일뿐입니다. 이미지 처리는 단순히 각 픽셀에 대해 새로운 색상을 계산하는 문제입니다. 각 픽셀의 새 색상은 기존 픽셀 색상, 주변 픽셀의 색상, 기타 매개 변수 또는 이러한 요소의 조합을 기반으로 할 수 있습니다.

2D API는 개발자가 이러한 이미지 픽셀을 조작하는 데 도움이되는 간단한 이미지 처리 모델을 도입합니다. 이 모델은 java.awt.image.BufferedImage클래스를 기반으로하며 컨볼 루션임계 값 과 같은 이미지 처리 작업 은 java.awt.image.BufferedImageOp인터페이스 구현으로 표현됩니다 .

이러한 작업의 구현은 비교적 간단합니다. 예를 들어 소스 이미지가 이미 BufferedImage호출 된 source. 위 그림에 표시된 작업을 수행하는 데는 몇 줄의 코드 만 필요합니다.

001 short [] 임계 값 = 새로운 short [256]; 002 for (int i = 0; i <256; i ++) 003 threshold [i] = (i <128)? (짧은) 0 : (짧은) 255; 004 BufferedImageOp thresholdOp = 005 new LookupOp (new ShortLookupTable (0, threshold), null); 006 BufferedImage 대상 = thresholdOp.filter (source, null);

그게 전부입니다. 이제 단계를 더 자세히 살펴 보겠습니다.

  1. 선택한 이미지 작업을 인스턴스화합니다 (004 및 005 행). 여기서는 LookupOpJava 2D 구현에 포함 된 이미지 작업 중 하나 인를 사용했습니다. 다른 이미지 작업과 마찬가지로 BufferedImageOp인터페이스를 구현합니다 . 이 작업에 대해서는 나중에 자세히 설명하겠습니다.

  2. filter()소스 이미지로 작업의 메서드를 호출합니다 (006 행). 소스가 처리되고 대상 이미지가 반환됩니다.

BufferedImage대상 이미지를 보관할을 이미 만든 경우 두 번째 매개 변수로 filter(). null위의 예에서와 같이 를 전달 하면 새 대상 BufferedImage이 생성됩니다.

2D API에는 이러한 몇 가지 기본 제공 이미지 작업이 포함되어 있습니다. 이 칼럼에서는 컨볼 루션, 조회 테이블임계 값의 세 가지에 대해 설명 합니다. 2D API (리소스)에서 사용 가능한 나머지 작업에 대한 정보는 Java 2D 문서를 참조하십시오.

회선

컨볼 루션 작업을 수행하면 대상 픽셀의 색을 결정하는 소스 픽셀과 이웃의 색상을 결합 할 수 있습니다. 이 조합은 대상 픽셀 색상을 계산하는 데 사용되는 각 소스 픽셀 색상의 비율을 결정하는 선형 연산자 인 커널을 사용하여 지정 됩니다.

커널은 한 번에 한 픽셀에서 컨볼 루션을 수행하기 위해 이미지 위에 오버레이되는 템플릿으로 생각하십시오. 각 픽셀이 복잡 해짐에 따라 템플릿은 소스 이미지의 다음 픽셀로 이동하고 컨볼 루션 프로세스가 반복됩니다. 이미지의 소스 사본은 컨볼 루션의 입력 값에 사용되며 모든 출력 값은 이미지의 대상 사본에 저장됩니다. 컨볼 루션 작업이 완료되면 대상 이미지가 반환됩니다.

커널의 중심은 복잡한 소스 픽셀을 오버레이하는 것으로 생각할 수 있습니다. 예를 들어 다음 커널을 사용하는 컨볼 루션 연산은 이미지에 영향을주지 않습니다. 각 대상 픽셀은 해당 소스 픽셀과 동일한 색상을 갖습니다.

 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 

커널을 만드는 기본 규칙은 이미지의 밝기를 유지하려면 요소가 모두 1이되어야한다는 것입니다.

2D API에서 컨볼 루션은 java.awt.image.ConvolveOp. ConvolveOp의 인스턴스로 표시되는 커널을 사용하여 를 구성 할 수 있습니다 java.awt.image.Kernel. 다음 코드 ConvolveOp는 위에 제시된 커널을 사용하여를 구성합니다 .

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

컨볼 루션 연산은 이미지에 대해 몇 가지 일반적인 연산을 수행하는 데 유용합니다. 이에 대해서는 잠시 후에 자세히 설명하겠습니다. 다른 커널은 근본적으로 다른 결과를 생성합니다.

이제 일부 이미지 처리 커널과 그 효과를 설명 할 준비가되었습니다. 수정되지 않은 이미지는 1892 년과 1893 년에 John Singer Sargent가 그린 Lochnaw의 Lady Agnew입니다 .

다음 코드 ConvolveOp는 동일한 양의 각 소스 픽셀과 인접 픽셀을 결합하는를 만듭니다 . 이 기술은 흐림 효과를 가져옵니다.

001 플로트 아홉 번째 = 1.0f / 9.0f; 002 float [] blurKernel = {003 아홉 번째, 아홉 번째, 아홉 번째, 004 아홉 번째, 아홉 번째, 아홉 번째, 005 아홉 번째, 아홉 번째, 아홉 번째 006}; 007 BufferedImageOp blur = new ConvolveOp (new Kernel (3, 3, blurKernel));

또 다른 일반적인 컨볼 루션 커널은 이미지의 가장자리를 강조합니다. 이 작업을 일반적으로 가장자리 감지 라고 합니다. 여기에 제시된 다른 커널과 달리이 커널의 계수는 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 = new ConvolveOp (new Kernel (3, 3, edgeKernel));

커널의 계수를 보면이 커널이하는 일을 알 수 있습니다 (002-004 행). 가장자리 감지 커널이 완전히 단색 인 영역에서 작동하는 데 어떻게 사용되는지 잠시 생각해보십시오. 주변 픽셀의 색상이 소스 픽셀의 색상을 취소하기 때문에 각 픽셀은 색상 (검정색)이 없게됩니다. 어두운 픽셀로 둘러싸인 밝은 픽셀은 밝게 유지됩니다.

원본과 비교하여 처리 된 이미지가 얼마나 더 어둡는지 확인하십시오. 이것은 에지 감지 커널의 요소가 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 short [] posterize = 새로운 short [256]; 002 for (int i = 0; i <256; i ++) 003 posterize [i] = (short) (i-(i % 32)); 004 BufferedImageOp posterizeOp = 005 new LookupOp (new ShortLookupTable (0, posterize), null);

임계 값

마지막으로 살펴볼 이미지 작업은 임계 값입니다. 임계 값은 프로그래머가 결정한 "경계"또는 임계 값에 걸쳐 색상 변경을 더 명확하게 만듭니다 (지도의 등고선이 고도 경계를 더 명확하게 만드는 방법과 유사). 이 기술은 지정된 임계 값, 최소값 및 최대 값을 사용하여 이미지의 각 픽셀에 대한 색상 구성 요소 값을 제어합니다. 임계 값 미만의 색상 값에는 최소값이 할당됩니다. 임계 값을 초과하는 값에는 최대 값이 할당됩니다.