diff options
Diffstat (limited to 'app/api/ocr/utils/imageRotation.ts')
| -rw-r--r-- | app/api/ocr/utils/imageRotation.ts | 244 |
1 files changed, 244 insertions, 0 deletions
diff --git a/app/api/ocr/utils/imageRotation.ts b/app/api/ocr/utils/imageRotation.ts new file mode 100644 index 00000000..fe9cf840 --- /dev/null +++ b/app/api/ocr/utils/imageRotation.ts @@ -0,0 +1,244 @@ +// app/api/ocr/utils/imageRotation.ts +// Sharp을 사용한 서버 사이드 이미지 회전 + +import sharp from 'sharp'; + +/** + * 서버 사이드에서 이미지를 회전시킵니다 + * @param base64 - base64 인코딩된 이미지 데이터 + * @param degrees - 회전 각도 (0, 90, 180, 270) + * @returns Promise<string> - 회전된 이미지의 base64 데이터 + */ +export async function rotateImageBase64(base64: string, degrees: number): Promise<string> { + try { + console.log(`🔄 Rotating image by ${degrees} degrees...`); + + // base64를 Buffer로 변환 + const inputBuffer = Buffer.from(base64, 'base64'); + + // 회전 각도에 따른 처리 + let rotatedBuffer: Buffer; + + switch (degrees) { + case 0: + // 회전 없음 + rotatedBuffer = inputBuffer; + break; + + case 90: + rotatedBuffer = await sharp(inputBuffer) + .rotate(90) + .jpeg({ + quality: 90, + progressive: true + }) + .toBuffer(); + break; + + case 180: + rotatedBuffer = await sharp(inputBuffer) + .rotate(180) + .jpeg({ + quality: 90, + progressive: true + }) + .toBuffer(); + break; + + case 270: + rotatedBuffer = await sharp(inputBuffer) + .rotate(270) + .jpeg({ + quality: 90, + progressive: true + }) + .toBuffer(); + break; + + default: + console.warn(`⚠️ Unsupported rotation angle: ${degrees}°. Using original image.`); + rotatedBuffer = inputBuffer; + } + + // Buffer를 다시 base64로 변환 + const rotatedBase64 = rotatedBuffer.toString('base64'); + + console.log(`✅ Image rotated successfully (${degrees}°)`); + return rotatedBase64; + + } catch (error) { + console.error(`❌ Error rotating image by ${degrees}°:`, error); + console.warn('Using original image due to rotation error'); + return base64; // 실패시 원본 반환 + } +} + +/** + * 이미지 품질을 개선합니다 + * @param base64 - base64 인코딩된 이미지 데이터 + * @returns Promise<string> - 개선된 이미지의 base64 데이터 + */ +export async function enhanceImageQuality(base64: string): Promise<string> { + try { + console.log('🎨 Enhancing image quality...'); + + const inputBuffer = Buffer.from(base64, 'base64'); + + const enhancedBuffer = await sharp(inputBuffer) + .resize(2000, 2000, { + fit: 'inside', + withoutEnlargement: true + }) + // 개별 매개변수 방식으로 수정 + .sharpen(1, 1, 2) // sigma, m1(flat), m2(jagged) + .normalize() // 히스토그램 정규화 + .gamma(1.1) // 약간의 감마 보정 + .jpeg({ + quality: 95, + progressive: true, + mozjpeg: true + }) + .toBuffer(); + + const enhancedBase64 = enhancedBuffer.toString('base64'); + + console.log('✅ Image quality enhanced'); + return enhancedBase64; + + } catch (error) { + console.error('❌ Error enhancing image:', error); + return base64; + } +} + +/** + * PDF를 고품질 이미지로 변환합니다 + * @param pdfBuffer - PDF Buffer 데이터 + * @param pageIndex - 변환할 페이지 인덱스 (0부터 시작) + * @returns Promise<string> - 변환된 이미지의 base64 데이터 + */ +export async function convertPDFToImage(pdfBuffer: Buffer, pageIndex: number = 0): Promise<string> { + try { + console.log(`📄 Converting PDF page ${pageIndex + 1} to image...`); + + // pdf2pic 라이브러리 사용 + const pdf2pic = require('pdf2pic'); + + const convert = pdf2pic.fromBuffer(pdfBuffer, { + density: 300, // 300 DPI for high quality + saveFilename: "page", + savePath: "/tmp", // 임시 경로 + format: "jpeg", + width: 2480, // A4 크기 @ 300 DPI + height: 3508, + quality: 100 + }); + + const result = await convert(pageIndex + 1, { responseType: "buffer" }); + const base64 = result.buffer.toString('base64'); + + console.log('✅ PDF converted to image successfully'); + return base64; + + } catch (error) { + console.error('❌ Error converting PDF to image:', error); + throw new Error('Failed to convert PDF to image'); + } +} + +/** + * 이미지에서 텍스트 방향을 감지합니다 + * @param base64 - base64 인코딩된 이미지 데이터 + * @returns Promise<number> - 감지된 올바른 회전 각도 + */ +export async function detectTextOrientation(base64: string): Promise<number> { + // 이 함수는 간단한 방향 감지를 시뮬레이션합니다 + // 실제로는 더 정교한 알고리즘이 필요할 수 있습니다 + + console.log('🧭 Detecting text orientation...'); + + const rotations = [0, 90, 180, 270]; + const scores: { rotation: number; score: number }[] = []; + + for (const rotation of rotations) { + try { + const rotatedBase64 = await rotateImageBase64(base64, rotation); + + // 간단한 품질 측정 (실제로는 OCR API 호출이나 다른 방법 사용) + const score = await estimateTextQuality(rotatedBase64); + scores.push({ rotation, score }); + + console.log(` ${rotation}°: quality score = ${score.toFixed(3)}`); + + } catch (error) { + console.warn(` ${rotation}°: Failed to test orientation`); + scores.push({ rotation, score: 0 }); + } + } + + const bestOrientation = scores.reduce((best, current) => + current.score > best.score ? current : best + ); + + console.log(`🎯 Best orientation detected: ${bestOrientation.rotation}°`); + return bestOrientation.rotation; +} + +/** + * 이미지의 텍스트 품질을 추정합니다 (간단한 버전) + */ +async function estimateTextQuality(base64: string): Promise<number> { + try { + const buffer = Buffer.from(base64, 'base64'); + + // Sharp을 사용해 이미지 통계 분석 + const stats = await sharp(buffer) + .greyscale() + .stats(); + + // 간단한 품질 지표 계산 + // 실제로는 더 복잡한 알고리즘이 필요합니다 + const contrast = stats.channels[0].max - stats.channels[0].min; + const sharpness = stats.channels[0].stdev; + + return (contrast + sharpness) / 510; // 0-1 범위로 정규화 + + } catch (error) { + return 0; + } +} + +/** + * 이미지가 회전이 필요한지 빠르게 체크합니다 + * @param base64 - base64 인코딩된 이미지 데이터 + * @returns Promise<boolean> - 회전이 필요하면 true + */ +export async function needsRotation(base64: string): Promise<boolean> { + try { + const buffer = Buffer.from(base64, 'base64'); + + // 이미지 메타데이터 확인 + const metadata = await sharp(buffer).metadata(); + + // EXIF 방향 정보가 있으면 회전 필요 + if (metadata.orientation && metadata.orientation > 1) { + console.log(`📐 EXIF orientation detected: ${metadata.orientation}`); + return true; + } + + // 가로/세로 비율 체크 (일반적으로 문서는 세로가 더 긺) + if (metadata.width && metadata.height) { + const aspectRatio = metadata.width / metadata.height; + if (aspectRatio > 1.5) { + console.log(`📐 Wide aspect ratio detected: ${aspectRatio.toFixed(2)}`); + return true; // 너무 가로로 긴 이미지는 회전 필요할 가능성 + } + } + + return false; + + } catch (error) { + console.warn('Error checking if rotation needed:', error); + return false; + } +}
\ No newline at end of file |
