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)를 사용하는 것이 좋습니다.
개선 포인트
- 경로 유효성 검증
imageFilePath와 pseudoColorImagePath의 경로가 허용된 범위인지 검증하는 로직 추가. - Mime Type 동적 처리
현재는 MediaType.IMAGE_PNG로 고정되어 있으나, 파일 확장자에 따라 동적으로 설정 가능. - 로깅 수준 조정
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에서 호출되어 실제 이미지 처리 작업을 수행합니다.
주요 기능
- 입력받은 이미지 파일에 PseudoColor 색상 맵을 적용.
- 처리된 이미지를 캐시에 저장하여 동일 요청 시 재처리 방지.
- 결과 이미지를 지정된 경로에 저장하고 파일 경로를 반환.
코드 분석
클래스 어노테이션
@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으로 변환.
- 적절한 커스텀 예외 클래스와 메시지를 제공하면 더 나은 사용자 경험을 제공할 수 있음.
주요 기술
- BufferedImage: Java 기본 이미지 처리 라이브러리.
- OpenCV: 고성능 이미지 처리 라이브러리.
- Spring Service: 비즈니스 로직 계층에서 처리 작업 수행.
- PseudoColor: 픽셀 값에 따라 다른 색상 맵핑을 적용하는 기술.
개선 포인트
- 캐시 정책
캐시 만료 기간을 설정하여 오래된 파일을 정리하는 로직 추가. - 예외 처리
현재는 모든 예외를 RuntimeException으로 던지지만, 적절한 커스텀 예외를 정의해 명확한 에러 원인을 제공. - 병렬 처리
큰 이미지 처리 시 성능 개선을 위해 픽셀 처리 부분에 멀티스레딩 적용 가능. - 테스트 케이스 작성
다양한 이미지 포맷, 색상 맵 타입, 에러 상황을 테스트하는 유닛 테스트 작성.
이 코드는 입력된 이미지에 PseudoColor를 적용하여 저장 및 반환하는 서비스를 구현하며, 이미지 처리와 캐싱을 효율적으로 처리하는 방법을 보여줍니다.
반응형