// server/utils/imagePreprocessor.js import sharp from 'sharp'; class ImagePreprocessor { constructor() { this.tempDir = './temp/processed'; this.logger = { info: (msg, ...args) => console.log(`🖼️ [预处理] ${msg}`, ...args), error: (msg, ...args) => console.error(`❌ [预处理] ${msg}`, ...args), debug: (msg, ...args) => console.debug(`❌ [预处理] ${msg}`, ...args) }; } async preprocessWithPadding(imagePath, config) { const startTime = Date.now(); this.logger.info(`开始预处理: ${imagePath}`); try { const metadata = await sharp(imagePath).metadata(); this.logger.info(`原始尺寸: ${metadata.width}x${metadata.height}`); // 智能填充策略 const { paddingX, paddingY } = this.calculateSmartPadding(metadata); const paddedWidth = metadata.width + paddingX * 2; const paddedHeight = metadata.height + paddingY * 2; this.logger.debug(`添加填充: ${paddingX}x${paddingY}, 新尺寸: ${paddedWidth}x${paddedHeight}`); const paddedBuffer = await sharp(imagePath) .extend({ top: paddingY, bottom: paddingY, left: paddingX, right: paddingX, background: { r: 255, g: 255, b: 255 } }) .png() .toBuffer(); const { width, height } = this.resizeForDetection({ width: paddedWidth, height: paddedHeight }, config); const resizedBuffer = await sharp(paddedBuffer) .resize(width, height) .png() .toBuffer(); const processingTime = Date.now() - startTime; this.logger.info(`预处理完成: ${width}x${height}, 耗时${processingTime}ms`); return { processedImage: { buffer: resizedBuffer, width, height, originalWidth: metadata.width, originalHeight: metadata.height, paddedWidth, paddedHeight, paddingX, paddingY, scaleX: paddedWidth / width, scaleY: paddedHeight / height } }; } catch (error) { this.logger.error('预处理错误', error); throw error; } } calculateSmartPadding(metadata) { const basePadding = 20; const minPadding = 15; // 根据图像尺寸动态调整填充 const widthRatio = Math.max(0.02, Math.min(0.08, 100 / metadata.width)); const heightRatio = Math.max(0.02, Math.min(0.08, 100 / metadata.height)); return { paddingX: Math.max(minPadding, Math.floor(metadata.width * widthRatio)), paddingY: Math.max(minPadding, Math.floor(metadata.height * heightRatio)) }; } resizeForDetection(metadata, config) { const { width, height } = metadata; const limitSideLen = config.detLimitSideLen || 960; let ratio = 1; if (Math.max(width, height) > limitSideLen) { ratio = limitSideLen / Math.max(width, height); this.logger.debug(`缩放比例: ${ratio.toFixed(4)}`); } const newWidth = Math.floor(width * ratio); const newHeight = Math.floor(height * ratio); // 确保尺寸是32的倍数 const finalWidth = Math.max(32, Math.floor(newWidth / 32) * 32); const finalHeight = Math.max(32, Math.floor(newHeight / 32) * 32); this.logger.debug(`调整后尺寸: ${finalWidth}x${finalHeight}`); return { width: finalWidth, height: finalHeight }; } async getImageInfo(imagePath) { try { const metadata = await sharp(imagePath).metadata(); return { width: metadata.width || 0, height: metadata.height || 0, format: metadata.format || 'unknown', processed: false }; } catch (error) { this.logger.error('获取图像信息失败', error); return { width: 0, height: 0, format: 'unknown', processed: false }; } } } export default ImagePreprocessor;