Conversation
- OCR 이미지 전처리 유틸리티 클래스 구현 - 이미지 품질 분석 기능 (해상도, 대비 검사) - 해상도 개선, 대비 향상, 노이즈 제거 기능 - OCR 정확도 향상을 위한 전처리 파이프라인 구축
Walkthrough
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Tip 🔌 Remote MCP (Model Context Protocol) integration is now available!Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats. ✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (7)
src/main/java/com/mumuk/global/util/ImagePreprocessingUtil.java (3)
28-29: 전처리 실패 시 기본값 처리 재고려 필요이미지 품질 분석 실패 시 무조건
true를 반환하면 불필요한 전처리가 수행될 수 있습니다. 실패 원인에 따라 원본 이미지를 사용하는 것이 더 나을 수 있습니다.} catch (IOException e) { log.error("❌ 전처리 필요 여부 판단 실패", e); - return true; // 안전하게 전처리 필요하다고 처리 + // 분석 실패 시 원본 사용을 위해 false 반환 + return false; }
98-123: 대비 검사 임계값 설정 가능하도록 개선하드코딩된 분산 임계값(1000)은 이미지 특성에 따라 적절하지 않을 수 있습니다. 설정 파일에서 조정 가능하도록 하는 것이 좋습니다.
+ @Value("${app.ocr.contrast-threshold:1000}") + private double contrastThreshold; + private boolean hasLowContrast(BufferedImage image) { // 간단한 대비 검사 로직 int[] histogram = new int[256]; // ... (히스토그램 계산 코드) - return variance < 1000; // 임계값은 조정 가능 + return variance < contrastThreshold; }
201-221: ImageQualityResult 클래스를 record로 변경 고려Java 14+ 환경에서는 간단한 데이터 클래스를 record로 구현하는 것이 더 간결합니다.
- public static class ImageQualityResult { - private final boolean sizeIssue; - private final boolean contrastIssue; - - public ImageQualityResult(boolean sizeIssue, boolean contrastIssue) { - this.sizeIssue = sizeIssue; - this.contrastIssue = contrastIssue; - } - - public boolean hasSizeIssue() { - return sizeIssue; - } - - public boolean hasContrastIssue() { - return contrastIssue; - } - - public boolean hasAnyIssue() { - return sizeIssue || contrastIssue; - } - } + public record ImageQualityResult(boolean sizeIssue, boolean contrastIssue) { + public boolean hasSizeIssue() { + return sizeIssue; + } + + public boolean hasContrastIssue() { + return contrastIssue; + } + + public boolean hasAnyIssue() { + return sizeIssue || contrastIssue; + } + }src/main/java/com/mumuk/domain/ocr/service/OcrServiceImpl.java (3)
25-76: 정규식 패턴 최적화 및 유지보수성 개선정규식 패턴들이 복잡하고 중복된 부분이 많습니다. 패턴을 외부 설정으로 관리하거나 더 체계적으로 구성하는 것이 좋습니다.
패턴 관리를 위한 별도 클래스 생성을 고려하세요:
@Component public class HealthPatternManager { private final Map<Pattern, String> patterns; public HealthPatternManager(@Value("${app.ocr.patterns-config}") Resource patternsConfig) { // JSON 또는 YAML 파일에서 패턴 로드 this.patterns = loadPatternsFromConfig(patternsConfig); } }
136-185: parseHealthDataUniversally 메서드의 성능 최적화 필요현재 구현은 모든 패턴에 대해 순차적으로 매칭을 시도하므로 O(n*m) 복잡도를 가집니다. 텍스트가 길거나 패턴이 많을 경우 성능 문제가 발생할 수 있습니다.
병렬 처리를 통한 성능 개선:
+ import java.util.concurrent.ConcurrentHashMap; + import java.util.stream.Collectors; + private Map<String, String> parseHealthDataUniversally(String rawText) { - Map<String, String> result = new LinkedHashMap<>(); - Set<String> usedValues = new HashSet<>(); + Map<String, String> result = new ConcurrentHashMap<>(); + Set<String> usedValues = ConcurrentHashMap.newKeySet(); log.info("🔍 범용 건강 데이터 파싱 시작"); String cleanedText = preprocessText(rawText); - // 각 패턴에 대해 매칭 시도 - for (Map.Entry<Pattern, String> entry : HEALTH_PATTERNS.entrySet()) { + // 병렬 처리로 성능 향상 + HEALTH_PATTERNS.entrySet().parallelStream().forEach(entry -> { Pattern pattern = entry.getKey(); String categoryPrefix = entry.getValue(); Matcher matcher = pattern.matcher(cleanedText); while (matcher.find()) { // ... (기존 로직) } - } + }); - log.info("🔍 파싱 완료, 추출된 항목 수: {}", result.size()); - return result; + // LinkedHashMap으로 변환하여 순서 유지 + Map<String, String> orderedResult = new LinkedHashMap<>(result); + log.info("🔍 파싱 완료, 추출된 항목 수: {}", orderedResult.size()); + return orderedResult; }
206-240: isValidHealthValue 메서드의 매직 넘버 제거하드코딩된 값들(0, 10000, 3)을 상수로 정의하여 가독성과 유지보수성을 향상시키세요.
+ private static final double MIN_VALID_VALUE = 0.0; + private static final double MAX_VALID_VALUE = 10000.0; + private static final int MAX_DECIMAL_PLACES = 3; + private boolean isValidHealthValue(String category, String key, String value) { try { double val = Double.parseDouble(value); double[] range = VALUE_RANGES.get(category); if (range != null) { // ... (기존 로직) } else { // 범위가 정의되지 않은 경우 기본 검사 - if (val <= 0 || val > 10000) { + if (val <= MIN_VALID_VALUE || val > MAX_VALID_VALUE) { log.debug("⚠️ 기본 범위 벗어남: {} = {}", key, val); return false; } } // 소수점 자릿수 체크 String[] parts = value.split("\\."); - if (parts.length > 1 && parts[1].length() > 3) { + if (parts.length > 1 && parts[1].length() > MAX_DECIMAL_PLACES) { log.debug("⚠️ 소수점 자릿수 초과: {} = {}", key, value); return false; }src/main/java/com/mumuk/global/client/ClovaOcrClient.java (1)
325-369: MultipartFile 구현의 잠재적 메모리 문제익명 클래스로 구현된 MultipartFile이 byte 배열을 직접 참조하고 있어 대용량 이미지 처리 시 메모리 문제가 발생할 수 있습니다.
Spring의
MockMultipartFile을 사용하거나 스트림 기반 구현을 고려하세요:+import org.springframework.mock.web.MockMultipartFile; + private MultipartFile createMultipartFileFromBytes(byte[] bytes, String originalFilename) { - return new MultipartFile() { - // ... 익명 클래스 구현 - }; + return new MockMultipartFile( + "file", + originalFilename, + "image/jpeg", + bytes + ); }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled
- Linear integration is disabled
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (5)
build.gradle(1 hunks)src/main/java/com/mumuk/domain/ocr/service/OcrServiceImpl.java(3 hunks)src/main/java/com/mumuk/global/client/ClovaOcrClient.java(4 hunks)src/main/java/com/mumuk/global/util/ImagePreprocessingUtil.java(1 hunks)src/main/resources/application.yml(1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/main/java/com/mumuk/global/client/ClovaOcrClient.java (2)
src/main/java/com/mumuk/global/util/FileResourceUtil.java (1)
FileResourceUtil(13-37)src/main/java/com/mumuk/domain/ocr/service/OcrServiceImpl.java (1)
Slf4j(17-280)
🔇 Additional comments (1)
build.gradle (1)
82-83: Spring Security 테스트 의존성 추가 확인OCR 기능 개선과 직접적인 연관은 없지만, Spring Security 테스트 의존성 추가는 적절합니다. 향후 OCR API 엔드포인트에 대한 보안 테스트를 위해 필요할 수 있습니다.
| public String callClovaOcrWithMultipleTemplates(MultipartFile imageFile) { | ||
| try { | ||
| log.info("다중 템플릿 OCR 시작"); | ||
|
|
||
| // 인바디 관련 여러 템플릿 시도 | ||
| int[] templateIds = {38491, 0}; // 38491: 기본 템플릿, 0: 범용 템플릿 | ||
|
|
||
| String bestResult = null; | ||
| int maxFieldCount = 0; | ||
|
|
||
| for (int templateId : templateIds) { | ||
| log.info("템플릿 {} 시도", templateId); | ||
| String result = callClovaOcrWithTemplate(imageFile, templateId); | ||
| int fieldCount = countExtractedFields(result); | ||
|
|
||
| log.info("템플릿 {} 결과: {} 필드 추출", templateId, fieldCount); | ||
|
|
||
| if (fieldCount > maxFieldCount) { | ||
| maxFieldCount = fieldCount; | ||
| bestResult = result; | ||
| } | ||
| } | ||
|
|
||
| log.info("최적 결과 선택: {} 필드", maxFieldCount); | ||
| return bestResult; | ||
|
|
||
| } catch (Exception e) { | ||
| log.warn("다중 템플릿 실패, 기본 템플릿 사용"); | ||
| return callClovaOcr(imageFile); | ||
| } | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
다중 템플릿 OCR의 하드코딩된 템플릿 ID
템플릿 ID(38491, 0)가 하드코딩되어 있습니다. 설정 파일에서 관리하는 것이 좋습니다.
+ @Value("${app.ocr.template-ids:38491,0}")
+ private int[] templateIds;
+
public String callClovaOcrWithMultipleTemplates(MultipartFile imageFile) {
try {
log.info("다중 템플릿 OCR 시작");
- // 인바디 관련 여러 템플릿 시도
- int[] templateIds = {38491, 0}; // 38491: 기본 템플릿, 0: 범용 템플릿
-
String bestResult = null;
int maxFieldCount = 0;
for (int templateId : templateIds) {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| public String callClovaOcrWithMultipleTemplates(MultipartFile imageFile) { | |
| try { | |
| log.info("다중 템플릿 OCR 시작"); | |
| // 인바디 관련 여러 템플릿 시도 | |
| int[] templateIds = {38491, 0}; // 38491: 기본 템플릿, 0: 범용 템플릿 | |
| String bestResult = null; | |
| int maxFieldCount = 0; | |
| for (int templateId : templateIds) { | |
| log.info("템플릿 {} 시도", templateId); | |
| String result = callClovaOcrWithTemplate(imageFile, templateId); | |
| int fieldCount = countExtractedFields(result); | |
| log.info("템플릿 {} 결과: {} 필드 추출", templateId, fieldCount); | |
| if (fieldCount > maxFieldCount) { | |
| maxFieldCount = fieldCount; | |
| bestResult = result; | |
| } | |
| } | |
| log.info("최적 결과 선택: {} 필드", maxFieldCount); | |
| return bestResult; | |
| } catch (Exception e) { | |
| log.warn("다중 템플릿 실패, 기본 템플릿 사용"); | |
| return callClovaOcr(imageFile); | |
| } | |
| } | |
| @Value("${app.ocr.template-ids:38491,0}") | |
| private int[] templateIds; | |
| public String callClovaOcrWithMultipleTemplates(MultipartFile imageFile) { | |
| try { | |
| log.info("다중 템플릿 OCR 시작"); | |
| String bestResult = null; | |
| int maxFieldCount = 0; | |
| for (int templateId : templateIds) { | |
| log.info("템플릿 {} 시도", templateId); | |
| String result = callClovaOcrWithTemplate(imageFile, templateId); | |
| int fieldCount = countExtractedFields(result); | |
| log.info("템플릿 {} 결과: {} 필드 추출", templateId, fieldCount); | |
| if (fieldCount > maxFieldCount) { | |
| maxFieldCount = fieldCount; | |
| bestResult = result; | |
| } | |
| } | |
| log.info("최적 결과 선택: {} 필드", maxFieldCount); | |
| return bestResult; | |
| } catch (Exception e) { | |
| log.warn("다중 템플릿 실패, 기본 템플릿 사용"); | |
| return callClovaOcr(imageFile); | |
| } | |
| } |
🤖 Prompt for AI Agents
In src/main/java/com/mumuk/global/client/ClovaOcrClient.java around lines 86 to
116, the template IDs (38491, 0) are hardcoded; move them into configuration and
inject them so they can be changed without code edits. Add a new property in
application.properties/yml (e.g. clova.ocr.template-ids=38491,0), add a field in
ClovaOcrClient to receive that value (List<Integer> or String that you parse
into ints) via @Value or @ConfigurationProperties, validate/parse the configured
values on bean init (fall back to a safe default if missing), and replace the
hardcoded int[] templateIds with the injected list when iterating; also log when
configuration is missing/invalid and ensure behavior remains the same (use
configured order and keep the existing fallback to callClovaOcr on exceptions).
| public String callClovaOcrWithRetry(MultipartFile imageFile, int maxRetries) { | ||
| Exception lastException = null; | ||
|
|
||
| for (int attempt = 1; attempt <= maxRetries; attempt++) { | ||
| try { | ||
| log.info("OCR 시도 {}/{}", attempt, maxRetries); | ||
|
|
||
| // 첫 번째 시도는 기본, 이후는 전처리 적용 | ||
| boolean enablePreprocessing = attempt > 1; | ||
| String result = callClovaOcrWithOptimization(imageFile, enablePreprocessing); | ||
|
|
||
| // 결과 품질 검증 | ||
| if (isValidOcrResult(result)) { | ||
| log.info("OCR 성공 (시도 {})", attempt); | ||
| return result; | ||
| } else { | ||
| log.warn("OCR 결과 품질 불량 (시도 {})", attempt); | ||
| if (attempt < maxRetries) { | ||
| Thread.sleep(1000 * attempt); // 점진적 대기 | ||
| } | ||
| } | ||
|
|
||
| } catch (Exception e) { | ||
| lastException = e; | ||
| log.warn("OCR 실패 (시도 {}): {}", attempt, e.getMessage()); | ||
|
|
||
| if (attempt < maxRetries) { | ||
| try { | ||
| Thread.sleep(2000 * attempt); // 점진적 대기 | ||
| } catch (InterruptedException ie) { | ||
| Thread.currentThread().interrupt(); | ||
| break; | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| throw new RuntimeException("OCR 최대 재시도 횟수 초과", lastException); | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
재시도 로직의 Thread.sleep 사용 문제
Thread.sleep()을 사용한 재시도 지연은 스레드를 블로킹하여 성능 문제를 일으킬 수 있습니다. Spring의 @Retryable 어노테이션이나 비동기 처리를 고려하세요.
+import org.springframework.retry.annotation.Backoff;
+import org.springframework.retry.annotation.Retryable;
+
- public String callClovaOcrWithRetry(MultipartFile imageFile, int maxRetries) {
- Exception lastException = null;
-
- for (int attempt = 1; attempt <= maxRetries; attempt++) {
- try {
- // ... 재시도 로직
- Thread.sleep(1000 * attempt);
- } catch (Exception e) {
- // ... 예외 처리
- }
- }
- throw new RuntimeException("OCR 최대 재시도 횟수 초과", lastException);
- }
+ @Retryable(
+ value = {RuntimeException.class},
+ maxAttempts = 3,
+ backoff = @Backoff(delay = 1000, multiplier = 2)
+ )
+ public String callClovaOcrWithRetry(MultipartFile imageFile) {
+ String result = callClovaOcrWithOptimization(imageFile, false);
+ if (!isValidOcrResult(result)) {
+ throw new RuntimeException("OCR 결과 품질 불량");
+ }
+ return result;
+ }Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In src/main/java/com/mumuk/global/client/ClovaOcrClient.java around lines 121 to
159, the current retry logic blocks threads using Thread.sleep; replace this
with a non-blocking retry strategy by refactoring the method to use Spring Retry
(annotate the OCR call method with @Retryable and configure maxAttempts and
Backoff for progressive waits) or implement an async retry using
CompletableFuture/ScheduledExecutorService so sleeps are not on the calling
thread; remove all Thread.sleep calls, surface InterruptedException handling
appropriately, and ensure the final failure still wraps the last exception and
is propagated.
| private int countExtractedFields(String ocrResult) { | ||
| try { | ||
| JsonNode root = objectMapper.readTree(ocrResult); | ||
| JsonNode fields = root.path("images").get(0).path("fields"); | ||
|
|
||
| if (fields.size() > 0) { | ||
| String text = fields.get(0).path("inferText").asText(); | ||
| // 인바디 관련 키워드 개수 계산 | ||
| String[] keywords = {"체중", "체지방", "골격근", "BMI", "단백질", "체수분", "무기질"}; | ||
| int count = 0; | ||
| for (String keyword : keywords) { | ||
| if (text.contains(keyword)) count++; | ||
| } | ||
| return count; | ||
| } | ||
| return 0; | ||
| } catch (Exception e) { | ||
| log.warn("필드 개수 계산 실패: {}", e.getMessage()); | ||
| return 0; | ||
| } | ||
| } |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
countExtractedFields 메서드의 키워드 하드코딩
건강 관련 키워드가 하드코딩되어 있어 OcrServiceImpl의 HEALTH_PATTERNS와 동기화되지 않을 수 있습니다.
키워드를 공통 상수로 관리하거나 설정 파일에서 관리하는 것이 좋습니다:
// 공통 상수 클래스
public class OcrConstants {
public static final String[] HEALTH_KEYWORDS = {
"체중", "체지방", "골격근", "BMI", "단백질", "체수분", "무기질"
};
}🏁 Script executed:
#!/bin/bash
# OcrServiceImpl과 ClovaOcrClient의 키워드 동기화 확인
echo "=== OcrServiceImpl의 건강 지표 패턴 ==="
rg -A1 'HEALTH_PATTERNS.put' src/main/java/com/mumuk/domain/ocr/service/OcrServiceImpl.java | grep -oP '"\K[^"]+(?=")'
echo "=== ClovaOcrClient의 키워드 ==="
rg -A2 'String\[\] keywords' src/main/java/com/mumuk/global/client/ClovaOcrClient.javaLength of output: 2941
공통 상수로 건강 키워드 관리 리팩터 필요
ClovaOcrClient의 countExtractedFields 메서드에서 하드코딩된 키워드 배열이 OcrServiceImpl의 HEALTH_PATTERNS에 정의된 패턴 목록과 일치하지 않아, 유지보수 및 동기화 누락 위험이 있습니다. 두 곳에서 동일한 키워드를 참조하도록 공통 상수 또는 설정 파일로 분리해주세요.
문제가 되는 위치:
- src/main/java/com/mumuk/global/client/ClovaOcrClient.java
String[] keywords = {"체중", "체지방", "골격근", "BMI", "단백질", "체수분", "무기질"};
- src/main/java/com/mumuk/domain/ocr/service/OcrServiceImpl.java
- 다수의
HEALTH_PATTERNS.put(...)에서 사용하는 키워드
- 다수의
예시 공통 상수 클래스:
package com.mumuk.global.constants;
public final class OcrConstants {
private OcrConstants() {}
public static final String[] HEALTH_KEYWORDS = {
"체중", "체지방량", "골격근량", "BMI", "단백질",
"체수분", "무기질", "체지방률", "기초대사율",
"수축기혈압", "이완기혈압", "혈당", "당화혈색소",
"총콜레스테롤", "HDL콜레스테롤", "LDL콜레스테롤",
"ALT", "AST", "크레아티닌", "요소질소",
"CRP", "TSH", "비타민D", "기타지표"
};
}- ClovaOcrClient와 OcrServiceImpl에서 위
OcrConstants.HEALTH_KEYWORDS를 참조하도록 변경해주세요.
🤖 Prompt for AI Agents
In src/main/java/com/mumuk/global/client/ClovaOcrClient.java around lines
266-286, the method currently uses a hardcoded String[] keywords which must be
replaced with a shared constant to avoid duplication; update the method to
reference com.mumuk.global.constants.OcrConstants.HEALTH_KEYWORDS instead of the
inline array, add the necessary import, and remove the local array; also ensure
src/main/java/com/mumuk/domain/ocr/service/OcrServiceImpl.java references the
same OcrConstants.HEALTH_KEYWORDS (or populates HEALTH_PATTERNS from it) so both
classes use the single shared constant and keep keyword lists synchronized.
| private BufferedImage removeNoise(BufferedImage image) { | ||
| // 간단한 블러 필터로 노이즈 제거 | ||
| BufferedImage denoised = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_RGB); | ||
|
|
||
| for (int y = 1; y < image.getHeight() - 1; y++) { | ||
| for (int x = 1; x < image.getWidth() - 1; x++) { | ||
| int totalR = 0, totalG = 0, totalB = 0; | ||
|
|
||
| for (int dy = -1; dy <= 1; dy++) { | ||
| for (int dx = -1; dx <= 1; dx++) { | ||
| int rgb = image.getRGB(x + dx, y + dy); | ||
| totalR += (rgb >> 16) & 0xFF; | ||
| totalG += (rgb >> 8) & 0xFF; | ||
| totalB += rgb & 0xFF; | ||
| } | ||
| } | ||
|
|
||
| int avgR = totalR / 9; | ||
| int avgG = totalG / 9; | ||
| int avgB = totalB / 9; | ||
|
|
||
| denoised.setRGB(x, y, (avgR << 16) | (avgG << 8) | avgB); | ||
| } | ||
| } | ||
|
|
||
| return denoised; |
There was a problem hiding this comment.
🛠️ Refactor suggestion
노이즈 제거 알고리즘의 엣지 처리 개선 필요
현재 구현은 이미지 경계(1픽셀)를 처리하지 않아 검은 테두리가 생길 수 있습니다.
private BufferedImage removeNoise(BufferedImage image) {
// 간단한 블러 필터로 노이즈 제거
BufferedImage denoised = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_RGB);
+ // 경계 픽셀 복사
+ for (int x = 0; x < image.getWidth(); x++) {
+ denoised.setRGB(x, 0, image.getRGB(x, 0));
+ denoised.setRGB(x, image.getHeight() - 1, image.getRGB(x, image.getHeight() - 1));
+ }
+ for (int y = 0; y < image.getHeight(); y++) {
+ denoised.setRGB(0, y, image.getRGB(0, y));
+ denoised.setRGB(image.getWidth() - 1, y, image.getRGB(image.getWidth() - 1, y));
+ }
+
for (int y = 1; y < image.getHeight() - 1; y++) {
for (int x = 1; x < image.getWidth() - 1; x++) {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| private BufferedImage removeNoise(BufferedImage image) { | |
| // 간단한 블러 필터로 노이즈 제거 | |
| BufferedImage denoised = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_RGB); | |
| for (int y = 1; y < image.getHeight() - 1; y++) { | |
| for (int x = 1; x < image.getWidth() - 1; x++) { | |
| int totalR = 0, totalG = 0, totalB = 0; | |
| for (int dy = -1; dy <= 1; dy++) { | |
| for (int dx = -1; dx <= 1; dx++) { | |
| int rgb = image.getRGB(x + dx, y + dy); | |
| totalR += (rgb >> 16) & 0xFF; | |
| totalG += (rgb >> 8) & 0xFF; | |
| totalB += rgb & 0xFF; | |
| } | |
| } | |
| int avgR = totalR / 9; | |
| int avgG = totalG / 9; | |
| int avgB = totalB / 9; | |
| denoised.setRGB(x, y, (avgR << 16) | (avgG << 8) | avgB); | |
| } | |
| } | |
| return denoised; | |
| private BufferedImage removeNoise(BufferedImage image) { | |
| // 간단한 블러 필터로 노이즈 제거 | |
| BufferedImage denoised = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_RGB); | |
| // 경계 픽셀 복사 | |
| for (int x = 0; x < image.getWidth(); x++) { | |
| denoised.setRGB(x, 0, image.getRGB(x, 0)); | |
| denoised.setRGB(x, image.getHeight() - 1, image.getRGB(x, image.getHeight() - 1)); | |
| } | |
| for (int y = 0; y < image.getHeight(); y++) { | |
| denoised.setRGB(0, y, image.getRGB(0, y)); | |
| denoised.setRGB(image.getWidth() - 1, y, image.getRGB(image.getWidth() - 1, y)); | |
| } | |
| for (int y = 1; y < image.getHeight() - 1; y++) { | |
| for (int x = 1; x < image.getWidth() - 1; x++) { | |
| int totalR = 0, totalG = 0, totalB = 0; | |
| for (int dy = -1; dy <= 1; dy++) { | |
| for (int dx = -1; dx <= 1; dx++) { | |
| int rgb = image.getRGB(x + dx, y + dy); | |
| totalR += (rgb >> 16) & 0xFF; | |
| totalG += (rgb >> 8) & 0xFF; | |
| totalB += rgb & 0xFF; | |
| } | |
| } | |
| int avgR = totalR / 9; | |
| int avgG = totalG / 9; | |
| int avgB = totalB / 9; | |
| denoised.setRGB(x, y, (avgR << 16) | (avgG << 8) | avgB); | |
| } | |
| } | |
| return denoised; | |
| } |
| ocr: | ||
| max-retries: 3 | ||
| retry-delay-ms: 2000 | ||
| enable-image-preprocessing: true | ||
| enable-multi-template: true | ||
| confidence-threshold: 0.7 |
There was a problem hiding this comment.
💡 Verification agent
❓ Verification inconclusive
OCR 설정값들의 적절성 검토
설정값들이 추가되었지만, 프로덕션 환경에서의 적절성을 검토해야 합니다:
max-retries: 3과retry-delay-ms: 2000는 최대 6초의 지연을 발생시킬 수 있습니다confidence-threshold: 0.7이 실제 코드에서 사용되지 않고 있습니다
🏁 Script executed:
#!/bin/bash
# confidence-threshold 설정이 코드에서 사용되는지 확인
rg -n "confidence-threshold" --type javaLength of output: 40
추가 검증을 위해 다음 스크립트를 실행해 주세요:
🏁 Script executed:
#!/bin/bash
# Spring ConfigurationProperties 어노테이션으로 ocr 설정이 바인딩되는 클래스 확인
rg -n '@ConfigurationProperties' -C2 --type java
# Java 코드 내에서 confidenceThreshold 필드(카멜케이스) 검색
rg -n 'confidenceThreshold' --type java
# @Value 어노테이션으로 직접 주입되는지 확인
rg -n "@Value.*confidence-threshold" --type javaLength of output: 139
OCR 설정 프로덕션 적합성 검토 및 미사용 프로퍼티 정리
설정값 검토가 필요합니다:
max-retries: 3×retry-delay-ms: 2000조합으로 요청 실패 시 최대 6초 지연이 발생할 수 있습니다. 프로덕션 SLA에 부합하는지 확인해 주세요.confidence-threshold: 0.7프로퍼티가 현재 코드에 바인딩되거나 사용되지 않고 있습니다. 실제 로직에서 사용하도록 구현하거나, 불필요하다면 설정에서 제거해 주세요.
🤖 Prompt for AI Agents
In src/main/resources/application.yml around lines 62 to 67, review and adjust
the OCR retry and confidence settings: verify that max-retries: 3 and
retry-delay-ms: 2000 (up to ~6s total delay) meet production SLA and either
lower max-retries or retry-delay-ms (or add exponential backoff/timeout caps) to
comply with latency requirements; also address confidence-threshold: 0.7 which
is not bound/used — either wire this property into the OCR decision logic (load
it into the OCR properties/config class and apply it where recognition results
are filtered/accepted) or remove the unused property from the YAML and code to
avoid dead configuration.
🎋 이슈 및 작업중인 브랜치
🔑 주요 내용
Check List
Summary by CodeRabbit
신규 기능
리팩터링
잡무(Chores)