Dev/Image-Processing

OpenCV로 Spring으로 Image Color 변환

DEV-HJ 2025. 1. 17. 16:24
반응형

PseudoColorController

package com.mirero.aegis.imageprocess.controller;

import com.mirero.aegis.imageprocess.domain.color.map.PseudoColorType;
import com.mirero.aegis.imageprocess.service.PseudoColorService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.net.MalformedURLException;
import java.nio.file.Path;
import java.nio.file.Paths;

@RestController
@RequestMapping("/api/v1/image-processing")
@RequiredArgsConstructor
@Slf4j
public class PseudoColorController {
    private final PseudoColorService pseudoColorService;

    @GetMapping("/pseudo/path")
    public ResponseEntity<Resource> getPseudoColorApplyImage(@RequestParam PseudoColorType colorMapType, @RequestParam String imageFilePath) throws MalformedURLException {
        log.info("colorMapType = {}, imageFilePath = {}", colorMapType, imageFilePath);
        String pseudoColorImagePath = pseudoColorService.getPseudoColorImage(colorMapType, imageFilePath);

        Path imagePath = Paths.get(pseudoColorImagePath);
        Resource resource = new UrlResource(imagePath.toUri());

        if (!resource.exists() || !resource.isReadable()) {
            throw new RuntimeException("이미지를 읽을 수 없습니다: " + pseudoColorImagePath);
        }

        return ResponseEntity.ok()
                .contentType(MediaType.IMAGE_PNG)
                .header(HttpHeaders.CONTENT_DISPOSITION, "inline;filename=" + resource.getFilename())
                .body(resource);
    }
}

주요 역할

  • 사용자로부터 색상 맵 타입(PseudoColorType) 및 **이미지 경로(imageFilePath)**를 입력받아, 해당 색상을 입힌 이미지 파일 경로를 반환하는 서비스(PseudoColorService)를 호출합니다.
  • 생성된 파일 경로를 사용하여 클라이언트에게 이미지 리소스를 반환합니다.

코드 분석

1. 클래스 어노테이션

@RestController
@RequestMapping("/api/v1/image-processing")
@RequiredArgsConstructor
@Slf4j
  • @RestController: RESTful API를 제공하는 컨트롤러임을 선언.
  • @RequestMapping: 기본 URL 경로를 설정. 여기서는 /api/v1/image-processing을 베이스로 사용.
  • @RequiredArgsConstructor: final 또는 @NonNull 필드를 초기화하는 생성자를 자동으로 생성.
  • @Slf4j: 로그 객체(log)를 자동으로 생성하여 로깅 기능을 사용할 수 있도록 지원.

2. 의존성 주입

private final PseudoColorService pseudoColorService;
  • PseudoColorService는 이미지 처리 로직을 담당하는 서비스로, 의존성을 생성자 주입 방식으로 관리.

3. API 엔드포인트

@GetMapping("/pseudo/path")
public ResponseEntity<Resource> getPseudoColorApplyImage(@RequestParam PseudoColorType colorMapType, @RequestParam String imageFilePath) throws MalformedURLException {
  • @GetMapping("/pseudo/path"): HTTP GET 요청을 처리하며 /api/v1/image-processing/pseudo/path 경로와 매핑.
  • @RequestParam: 요청 매개변수를 받아옴.
    • colorMapType: 색상 맵의 타입.
    • imageFilePath: 원본 이미지 파일 경로.
  • 반환 타입: ResponseEntity<Resource>를 통해 HTTP 응답 본문에 이미지 리소스를 포함.

4. 서비스 호출

String pseudoColorImagePath = pseudoColorService.getPseudoColorImage(colorMapType, imageFilePath);
  • pseudoColorService.getPseudoColorImage: 입력받은 colorMapType과 imageFilePath를 기반으로 처리된 이미지 파일의 경로를 반환.

5. 리소스 처리

Path imagePath = Paths.get(pseudoColorImagePath);
Resource resource = new UrlResource(imagePath.toUri());

if (!resource.exists() || !resource.isReadable()) {
    throw new RuntimeException("이미지를 읽을 수 없습니다: " + pseudoColorImagePath);
}
P
  • Paths.get(pseudoColorImagePath): 문자열 파일 경로를 Path 객체로 변환.
  • UrlResource: 이미지 파일을 리소스로 변환.
  • 파일이 존재하지 않거나 읽기 불가능한 경우 예외를 발생.

6. HTTP 응답 반환

return ResponseEntity.ok()
        .contentType(MediaType.IMAGE_PNG)
        .header(HttpHeaders.CONTENT_DISPOSITION, "inline;filename=" + resource.getFilename())
        .body(resource);
 
  • ResponseEntity를 통해 HTTP 상태 코드 200(OK)와 함께 응답 본문에 리소스를 포함.
  • MediaType.IMAGE_PNG: 응답 콘텐츠 타입을 image/png로 지정.
  • Content-Disposition: 브라우저가 파일을 인라인으로 표시하도록 설정.

예외 처리

현재 코드에서는 파일이 존재하지 않거나 읽을 수 없는 경우 RuntimeException을 발생시킵니다.
이를 보완하기 위해 Spring의 전역 예외 처리(@ControllerAdvice)를 사용하는 것이 좋습니다.


개선 포인트

  1. 경로 유효성 검증
    imageFilePath와 pseudoColorImagePath의 경로가 허용된 범위인지 검증하는 로직 추가.
  2. Mime Type 동적 처리
    현재는 MediaType.IMAGE_PNG로 고정되어 있으나, 파일 확장자에 따라 동적으로 설정 가능.
  3. 로깅 수준 조정
    log.info 대신 디버그 레벨(log.debug)을 사용하거나 민감한 정보를 숨길 수 있는 로직 추가.

이 코드의 기능은 클라이언트가 요청한 이미지를 처리하여 PseudoColor 적용 결과를 반환하는 것입니다. Spring Boot 기반 프로젝트에서 이미지 처리와 관련된 간단한 API를 구현할 때 유용한 구조입니다.


PseudoColorServiceImpl

package com.mirero.aegis.imageprocess.service;

import com.mirero.aegis.imageprocess.domain.color.ColorMap;
import com.mirero.aegis.imageprocess.domain.color.map.PseudoColorType;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.bytedeco.javacpp.BytePointer;
import org.bytedeco.opencv.global.opencv_core;
import org.bytedeco.opencv.global.opencv_imgcodecs;
import org.bytedeco.opencv.opencv_core.Mat;
import org.springframework.stereotype.Service;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.io.File;

@Slf4j
@Service
@RequiredArgsConstructor
public class PseudoColorServiceImpl implements PseudoColorService {

    private static final String pseudoColorCachePath = "cache/pseudo/";

    @Override
    public String getPseudoColorImage(PseudoColorType type, String imageFilePath) {
        int value = 0;
        try {
            // 로컬 파일 경로 생성
            String fileName = new File(imageFilePath).getName();
            String checkedFilePath = String.format("%s%s/%s", pseudoColorCachePath, type, fileName);
            int[] colorMap = ColorMap.getColorMap(type);

            // 캐시 파일 존재 여부 확인
            File cachedFile = new File(checkedFilePath);
            if (cachedFile.exists()) {
                log.info("캐시된 파일 반환: {}", cachedFile.getAbsolutePath());
                return cachedFile.getAbsolutePath();
            }

            // 원본 이미지 읽기
            BufferedImage image = ImageIO.read(new File(imageFilePath));
            byte[] imageBuffers = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();

            Mat dstImage = new Mat(image.getHeight(), image.getWidth(), opencv_core.CV_8UC3);
            BytePointer dstPointer = dstImage.data();

            for (int y = 0; y < dstImage.rows(); y++) {
                for (int x = 0; x < dstImage.cols(); x++) {
                    int index = y * dstImage.cols() * 3 + x * 3;
                    value = imageBuffers[y * dstImage.cols() + x] & 0xFF;
                    dstPointer.put(index, (byte) (colorMap[value] >> 16));
                    dstPointer.put(index + 1, (byte) (colorMap[value] >> 8));
                    dstPointer.put(index + 2, (byte) (colorMap[value]));
                }
            }

            // 결과 이미지 저장
            File outputDir = new File(checkedFilePath).getParentFile();
            if (!outputDir.exists()) {
                outputDir.mkdirs();
            }

            // OpenCV Mat을 이미지 파일로 저장
            opencv_imgcodecs.imwrite(checkedFilePath, dstImage);

            log.info("PseudoColor Cache UploadFilePath: {}", checkedFilePath);
            return checkedFilePath;
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());
        }
    }
}

이 코드는 특정 이미지를 입력받아 PseudoColor 처리를 수행한 후 결과 이미지를 반환하거나 캐시에 저장된 결과를 사용하는 서비스 로직입니다. 이 서비스는 PseudoColorController에서 호출되어 실제 이미지 처리 작업을 수행합니다.


주요 기능

  1. 입력받은 이미지 파일에 PseudoColor 색상 맵을 적용.
  2. 처리된 이미지를 캐시에 저장하여 동일 요청 시 재처리 방지.
  3. 결과 이미지를 지정된 경로에 저장하고 파일 경로를 반환.

코드 분석

클래스 어노테이션

@Slf4j
@Service
@RequiredArgsConstructor
  • @Slf4j: 로깅 기능 지원. log.info, log.error 등을 통해 로그를 기록.
  • @Service: Spring의 서비스 계층 컴포넌트로 선언.
  • @RequiredArgsConstructor: final 또는 @NonNull 필드를 초기화하는 생성자를 자동 생성.

상수 선언

private static final String pseudoColorCachePath = "cache/pseudo/";
  • pseudoColorCachePath: PseudoColor 처리 결과 이미지를 저장할 디렉토리.

메소드: getPseudoColorImage

메소드 개요

입력 매개변수:

  • PseudoColorType type: PseudoColor 색상 맵 타입.
  • String imageFilePath: 원본 이미지 파일 경로.

반환값:

  • 결과 이미지의 저장 경로(String).

1. 파일 경로 및 캐시 경로 설정

String fileName = new File(imageFilePath).getName();
String checkedFilePath = String.format("%s%s/%s", pseudoColorCachePath, type, fileName);
int[] colorMap = ColorMap.getColorMap(type);
  • fileName: 원본 이미지의 파일명.
  • checkedFilePath: 처리된 결과 이미지의 저장 경로.
    • 예: cache/pseudo/<type>/<fileName>.
  • ColorMap.getColorMap(type): 입력받은 색상 맵 타입에 대한 색상 배열을 반환.

2. 캐시 확인

File cachedFile = new File(checkedFilePath);
if (cachedFile.exists()) {
    log.info("캐시된 파일 반환: {}", cachedFile.getAbsolutePath());
    return cachedFile.getAbsolutePath();
}
  • 지정된 경로에 동일한 요청의 처리 결과가 이미 존재하면 해당 파일 경로를 반환.

3. 원본 이미지 읽기

BufferedImage image = ImageIO.read(new File(imageFilePath));
byte[] imageBuffers = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
  • ImageIO.read: 원본 이미지를 BufferedImage로 읽어옴.
  • DataBufferByte: 이미지 데이터를 바이트 배열로 변환.

4. OpenCV를 사용한 PseudoColor 처리

Mat dstImage = new Mat(image.getHeight(), image.getWidth(), opencv_core.CV_8UC3);
BytePointer dstPointer = dstImage.data();

for (int y = 0; y < dstImage.rows(); y++) {
    for (int x = 0; x < dstImage.cols(); x++) {
        int index = y * dstImage.cols() * 3 + x * 3;
        value = imageBuffers[y * dstImage.cols() + x] & 0xFF;
        dstPointer.put(index, (byte) (colorMap[value] >> 16));
        dstPointer.put(index + 1, (byte) (colorMap[value] >> 8));
        dstPointer.put(index + 2, (byte) (colorMap[value]));
    }
}
  • Mat dstImage: OpenCV의 이미지 데이터 구조로, 처리된 결과를 저장.
  • BytePointer dstPointer: OpenCV 이미지 데이터의 포인터.
  • 이중 반복문(rows와 cols): 원본 이미지의 픽셀별 데이터를 읽고 색상 맵을 적용.
    • colorMap[value]: 색상 맵 배열에서 해당 픽셀 값에 매핑된 색상 값을 가져옴.
    • >>: 비트 연산으로 RGB 값을 분리.

5. 결과 이미지 저장

File outputDir = new File(checkedFilePath).getParentFile();
if (!outputDir.exists()) {
    outputDir.mkdirs();
}
opencv_imgcodecs.imwrite(checkedFilePath, dstImage);
  • 처리 결과를 지정된 경로(checkedFilePath)에 저장.
  • 디렉토리가 존재하지 않으면 mkdirs()를 통해 생성.

6. 처리 결과 반환

log.info("PseudoColor Cache UploadFilePath: {}", checkedFilePath);
return checkedFilePath;
  • 결과 이미지 파일의 경로를 반환.

예외 처리

catch (Exception e) {
    throw new RuntimeException(e.getMessage());
}
  • 처리 과정에서 발생한 모든 예외를 포착하여 RuntimeException으로 변환.
  • 적절한 커스텀 예외 클래스와 메시지를 제공하면 더 나은 사용자 경험을 제공할 수 있음.

주요 기술

  1. BufferedImage: Java 기본 이미지 처리 라이브러리.
  2. OpenCV: 고성능 이미지 처리 라이브러리.
  3. Spring Service: 비즈니스 로직 계층에서 처리 작업 수행.
  4. PseudoColor: 픽셀 값에 따라 다른 색상 맵핑을 적용하는 기술.

개선 포인트

  1. 캐시 정책
    캐시 만료 기간을 설정하여 오래된 파일을 정리하는 로직 추가.
  2. 예외 처리
    현재는 모든 예외를 RuntimeException으로 던지지만, 적절한 커스텀 예외를 정의해 명확한 에러 원인을 제공.
  3. 병렬 처리
    큰 이미지 처리 시 성능 개선을 위해 픽셀 처리 부분에 멀티스레딩 적용 가능.
  4. 테스트 케이스 작성
    다양한 이미지 포맷, 색상 맵 타입, 에러 상황을 테스트하는 유닛 테스트 작성.

이 코드는 입력된 이미지에 PseudoColor를 적용하여 저장 및 반환하는 서비스를 구현하며, 이미지 처리와 캐싱을 효율적으로 처리하는 방법을 보여줍니다.

반응형