올해가 절반을 지나가는 이시점에 나는 6라운드 중 4라운드를 마무리하고 자바스터디 마무리를 향해 달려가고 있다:) 피곤하고 힘들지만 조금만 더 힘을 내자! 이번 라운드는 이미지 프로세싱, 말로만 듣던 영상처리이다. 뭔가 이름에서 묵직함이 느껴지는데 그만큼 이 공부 기간도 쉽지 않았던 것 같다.

그럼 과제 내용을 소개하겠다.

포토샵 만들기!

기한 : 6/27(오후 9시) ~ 6/30(오전11시)

기능(택 4) :

  • 이미지 파일 읽어오기
  • 흑백영상
  • 엣지추출
  • contrast, 밝기 조절
  • 영상 합성
  • 돋보기
  • 야심작 + 알파

이번 라운드 목표는 기능구현 그 이상으로 야심작을 잘 구현해내서 보여주는 것과 GUI를 잘 구성하여 시각화 하는 것이었다!! (이런거 잘하는 친구들이 너무 부러웠기 때문이다..ㅎㅎ)

그럼 일단 결과물부터 보고가자!

사진

나름 잘 예쁘지 않나..!?ㅎㅎㅎㅎㅎ

일단 나는 나름 만족했다.

사진

짜잔 ㅎㅎㅎㅎ

일단 처음에 이미지를 어떻게 수정하는지 막막해서 여러 구글링을 해본 결과 각 사진은 작은 픽셀들로 구성되어 있는데 그 픽셀들의 RGB값을 바꿔주면 이미지의 색상을 바꿀 수 있다로 한다.

RGB란 red green blue 색상을 섞은 것을 의미하는데 각 색이 255에 가까울수록 연해지고 0에 가까울수록 어두워진다. (나는 직관적으로 0이 흰색이라 생각했는데 정반대다..쳇…)

일단

기능은 다음과 같다!

1. 로드 파일!

사진

파일을 로드할 수 있게 해준다. 이것은 JFileChooser를 활용했다.

FileNameExtensionFilter filter = new FileNameExtensionFilter("JPG&GIF Images", "jpg", "gif", "jpeg");
chooser.setFileFilter(filter);
int ret = chooser.showOpenDialog(null);
if(ret!= JFileChooser.APPROVE_OPTION) {
JOptionPane.showMessageDialog(null, "파일을 선택하지 않았습니다.", "경고", JOptionPane.WARNING_MESSAGE);
return;
} 
filePath=chooser.getSelectedFile().getPath();
try {
Image resizeImage;
BufferedImage image;
File input = new File(filePath);
image = ImageIO.read(input);
resizeImage = image.getScaledInstance(w, h, Image.SCALE_SMOOTH);
newImage = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
Graphics g = newImage.getGraphics();
g.drawImage(resizeImage, 0, 0, null);
g.dispose(); 
}catch....

나는 모든 사진 파일을 저 560X720 판넬에 넣기 위해서 resizeImage를 getScaledInstance명령을 이용해서 생성해주었고 그 이미지를 newImage라는 bufferedImage로 저장해주었다!

2. Gray Scale

사진

다음은 그레이스케일이다.

이건 내가 진행했던 이미지 프로세싱 중에서 제법 쉬운 축에 속했다.

for문 두개를 활용해서 각 픽셀의 rgb값을 받아온뒤에 red, green, blue에 특정 값(인터넷에서 찾았다..ㅎㅎ)을 곱해준 후에 그 값을 다시 rgb값으로 변환해서 이미지에 저장해주면 흑백사진이 만들어진다!

for(int i=0; i<height; i++) {
for(int j=0; j<width; j++) {
Color c = new Color(newImage.getRGB(j, i));
int red = (int)(c.getRed() * 0.299);
int green = (int)(c.getGreen() * 0.587);
int blue = (int)(c.getBlue() * 0.114);
Color newColor = new Color(red+green+blue, red+green+blue, red+green+blue);
changedImage.setRGB(j, i, newColor.getRGB());
}
}
Graphics2D g6 = (Graphics2D)drawPanel2.getGraphics();
g6.drawImage(changedImage, 0, 0, null);

3. Edge Detect

사진

일단 제일 공을 많이 들인 부분이다.

이해하는데 너무 오래걸렸다….(물론 길어야 하루도 안갔지만)

Convolution과 Kernel을 활용해보았다!! Convolution에 대한 이해는 내가 시켜주는 것보다 내가 공부했던 사이트를 첨부해주겠다.

https://www.youtube.com/watch?v=C_zFhWdM4ic&t=43s

https://m.blog.naver.com/kjh3864/220974040454

일단 내가 이해한 바를 조금 설명해보자면 Convolution을 활용한다함은 음..

filter에 해당하는 틀(3x3, 5x5, …)을 만들어서 A 픽셀의 주변 색상값에 미리 설정해준 필터값을 곱한 뒤 평균을 구해서 A 픽셀의 색상으로 설정해주는 것이다. 근데 이때 필터라는 Array값을 잘 변경하다보면 사진 색상에 다양한 변화를 줄 수 있다.

나는 위 블로그의 코드를 인용해서 코드를 짰는데

double[][] filterEdge = 
{ { -1, -1, -1 }, 
{ -1,  8, -1  }, 
{ -1, -1, -1  }}; 

필터의 값을 다음과 같이 설정해주어서 결과를 얻어낼 수 있었다!

그리고 미리 edge를 좀 뚜렸하게해주기 위해 sharpenfilter를 따로 만들어 한번 진행하고 엣지필터를 입히니 좀더 선명하게 엣지를 찾을 수 있었다.

4. 밝기 & Contrast 조절

사진

밝기는 쉬운 편이었다! 픽셀 Red, Green, Blue에 입력받은 bright값을 추가하는 식으로 진행했다.

for(int y = 0 ; y < newImage.getHeight(); y++) {
for(int x = 0; x < newImage.getWidth(); x++) {
Color c = new Color(newImage.getRGB(x, y));


red[y][x] = c.getRed() + bright;
blue[y][x] = c.getBlue() + bright;
green[y][x] = c.getGreen() + bright;
red[y][x] = Math.max(0, red[y][x]); 
blue[y][x] = Math.max(0, blue[y][x]); 
green[y][x] = Math.max(0, green[y][x]);
red[y][x] = Math.min(255, red[y][x]); 
blue[y][x] = Math.min(255, blue[y][x]); 
green[y][x] = Math.min(255, green[y][x]);
Color d = new Color(red[y][x], green[y][x], blue[y][x]);
tempImage.setRGB(x, y, d.getRGB());
}
}

그리고 contrast는 RescaleOp라는 bufferedImageOp filter 메소드를 이용해서 구현해보았다.

RescaleOp rescale = new RescaleOp((float)(contrast/100), 0, null);
return rescale.filter(tempImage, null);

5. Look Up & Distinct 기능

사진

사진

룩업과 디스팅트는 각각 BufferedImageOp 필터를 활용해서 만들어주었는데 이것은 awt에서 제공하고 있는 아주 유용한 메소드이다.

short[] data = new short[256];
for (short i = 0; i < 256; i++) {
data[i] = (short) (255 - i);
}


LookupTable lookupTable = new ShortLookupTable(0, data);
LookupOp op = new LookupOp(lookupTable, null);
changedImage = op.filter(changedImage, null);
Graphics2D g7 = (Graphics2D)drawPanel2.getGraphics();
g7.drawImage(changedImage, 0, 0, null);

BufferedImageFilter에 대한 활용은 다음 사이트를 참고하길 바란다!

http://www.informit.com/articles/article.aspx?p=1013851&seqNum=2

6. 돋보기 기능

사진

돋보기와 옆에 미리보기 창은 픽셀을 따로 건드리지 않고 bufferedImage의 메소드를 활용해서 만들어봤다!!

먼저, 이미지의 부분을 가져올 수 있는 getSubimage 메소드를 활용해서 마우스가 있는 위치 부분을 subimage를 저장한 후에 그 이미지를 확대하여 출력하는 식으로 했다.

getScaledInstance 메소드를 이용하면 원하는 사이즈로 확대가 가능하다!!

그리고 원형으로 돋보기를 만들기 위해서 Ellipse2D를 만들어서 그안에 이미지를 그려주었다!ㅎㅎ

내 방법이 꽤 괜찮은 거 같아서 뿌듯하다:)

if(expand == 1) {
    if(mx>25 && my>25 && mx+25<560 && my+25<720) {
        BufferedImage temp = changedImage.getSubimage(mx-25, my-25, 50, 50);
        Image image = temp.getScaledInstance(expwidth, expwidth, Image.SCALE_SMOOTH);
        expcircle = new Ellipse2D.Float(mx - expwidth/2, my - expwidth/2, expwidth, expwidth);
        g2.setClip(expcircle);
        g2.drawImage(image, mx-expwidth/2, my-expwidth/2, expwidth, expwidth, null);
    }
}

7. 여드름 패치(야심작!!)

사진

다음은 여드름 패치기능이다.. 이번 라운드 나의 최종변기이자 야심작이다 ㅎㅎ 데모 때 나름 반응도 괜찮았다:) ㅎㅎㅎ

일단 기능은 다음과 같이 이미지에 나온 여드름과 같은 잡티를 없애주는 기능이다ㅎㅎ

무엇보다 기능은 blur을 이용했다. convolution을 이해한 것을 기반으로 필터를 모든 원소가 1인 3X3 배열로 설정해주어서 주변값과 비슷한 색상으로 마우스가 위치한 일부지점 값만 변경해주었다 ㅎㅎ

나름 성공적이었다. 그리고 생각보다 여드름이 잘지워져서 나도 놀란 신기한 기능이다.

먼저 bufferedImage의 모든 픽셀 값을 받은 후에 일부만 수정하고 다시 다 돌려 넣어주는 것으로 진행했다!

public void toColorArray() {
    for(int y = 0; y < height; y++) {
        for(int x = 0; x < width; x++) {
            Color c = new Color(changedImage.getRGB(x, y));
            red[y][x] = c.getRed();
            blue[y][x] = c.getBlue();
            green[y][x] = c.getGreen();
            outred[y][x] = c.getRed();
            outblue[y][x] = c.getBlue();
            outgreen[y][x] = c.getGreen();
        }
    }


    for(int y = my - 3 ; y < my + 3; y++) {
        for(int x = mx - 3 ; x < mx + 3; x++) {
            outred[y][x] = 0;
            outgreen[y][x] = 0;
            outblue[y][x] = 0;
        }
    }


    for(int y = my - 3 ; y < my + 3; y++) {
        for(int x = mx - 3 ; x < mx + 3; x++) {
            for(int i = 0; i< patch; i++) {
                for(int j = 0; j < patch; j++) {
                    outred[y][x] += red[y-1+i][x-1+j]/(patch*patch);
                    outblue[y][x] += blue[y-1+i][x-1+j]/(patch*patch);
                    outgreen[y][x] += green[y-1+i][x-1+j]/(patch*patch);
                }
            }


        }
    }


    for(int y = 0; y < height; y++) {
        for(int x = 0; x < width; x++) {
            changedImage.setRGB(x, y, new Color((int) outred[y][x], (int) outgreen[y][x], (int) outblue[y][x]).getRGB());
        }
    }


    Graphics2D g7 = (Graphics2D)drawPanel2.getGraphics();
    g7.drawImage(changedImage, 0, 0, null);
}

코드는 잘 못짰다.. 실력이 부족한 건 이해바란다 ㅎㅎㅎㅎ

그리고 이번 코드를 하면서 가장 막혔던 부분!!

BufferedImage Copy방법이다!!

카피에 두종류가 있는데 shallow copy와 deep copy가 있다. shallow copy는 a를 카피해서 b에 저장한 후에 a값을 수정하면 b값도 수정된다..(전혀 원하지 않았다..) 그래서 필요한 것이 deep copy이다. deep copy는 복사 후에도 값이 수정되지 않는다!

버퍼드이미지를 deepcopy 하기 위해서 다음 메소드를 사용했다!

public static BufferedImage deepCopy(BufferedImage bi) {
ColorModel cm = bi.getColorModel();
boolean isAlphaPremultiplied = cm.isAlphaPremultiplied();
WritableRaster raster = bi.copyData(bi.getRaster().createCompatibleWritableRaster());
return new BufferedImage(cm, raster, isAlphaPremultiplied, null);
}

bufferedimage를 완벽하게 복사하고 싶다면 다음 메소드를 사용하면 좋겠다!!

후우.. 드디어 4라운드를 마무리했다. 여태까지 했던 것 중에 공부 과정이 제일 힘들었고 문제가 있어도 이미지 어디가 문제인지 알기가 쉽지 않아서 어려웠던 과제였다. 그래도 이미지를 수정한다는 것이 어떤 느낌인지 대강은 이해할 수 있었던 아주 귀한 기회였다고 생각한다. 다음 라운드도 최선을 다해서 진행해보자. 화이팅!(일단 주말 쉬고:))

코드는 github에 게시해놨다.

https://github.com/gur5381/JavaStudy18-1