imagePreprocessor.js 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. // server/utils/imagePreprocessor.js
  2. import sharp from 'sharp';
  3. class ImagePreprocessor {
  4. constructor() {
  5. this.tempDir = './temp/processed';
  6. this.logger = {
  7. info: (msg, ...args) => console.log(`🖼️ [预处理] ${msg}`, ...args),
  8. error: (msg, ...args) => console.error(`❌ [预处理] ${msg}`, ...args),
  9. debug: (msg, ...args) => console.debug(`❌ [预处理] ${msg}`, ...args)
  10. };
  11. }
  12. async preprocessWithPadding(imagePath, config) {
  13. const startTime = Date.now();
  14. this.logger.info(`开始预处理: ${imagePath}`);
  15. try {
  16. const metadata = await sharp(imagePath).metadata();
  17. this.logger.info(`原始尺寸: ${metadata.width}x${metadata.height}`);
  18. // 智能填充策略
  19. const { paddingX, paddingY } = this.calculateSmartPadding(metadata);
  20. const paddedWidth = metadata.width + paddingX * 2;
  21. const paddedHeight = metadata.height + paddingY * 2;
  22. this.logger.debug(`添加填充: ${paddingX}x${paddingY}, 新尺寸: ${paddedWidth}x${paddedHeight}`);
  23. const paddedBuffer = await sharp(imagePath)
  24. .extend({
  25. top: paddingY,
  26. bottom: paddingY,
  27. left: paddingX,
  28. right: paddingX,
  29. background: { r: 255, g: 255, b: 255 }
  30. })
  31. .png()
  32. .toBuffer();
  33. const { width, height } = this.resizeForDetection({
  34. width: paddedWidth,
  35. height: paddedHeight
  36. }, config);
  37. const resizedBuffer = await sharp(paddedBuffer)
  38. .resize(width, height)
  39. .png()
  40. .toBuffer();
  41. const processingTime = Date.now() - startTime;
  42. this.logger.info(`预处理完成: ${width}x${height}, 耗时${processingTime}ms`);
  43. return {
  44. processedImage: {
  45. buffer: resizedBuffer,
  46. width, height,
  47. originalWidth: metadata.width,
  48. originalHeight: metadata.height,
  49. paddedWidth, paddedHeight,
  50. paddingX, paddingY,
  51. scaleX: paddedWidth / width,
  52. scaleY: paddedHeight / height
  53. }
  54. };
  55. } catch (error) {
  56. this.logger.error('预处理错误', error);
  57. throw error;
  58. }
  59. }
  60. calculateSmartPadding(metadata) {
  61. const basePadding = 20;
  62. const minPadding = 15;
  63. // 根据图像尺寸动态调整填充
  64. const widthRatio = Math.max(0.02, Math.min(0.08, 100 / metadata.width));
  65. const heightRatio = Math.max(0.02, Math.min(0.08, 100 / metadata.height));
  66. return {
  67. paddingX: Math.max(minPadding, Math.floor(metadata.width * widthRatio)),
  68. paddingY: Math.max(minPadding, Math.floor(metadata.height * heightRatio))
  69. };
  70. }
  71. resizeForDetection(metadata, config) {
  72. const { width, height } = metadata;
  73. const limitSideLen = config.detLimitSideLen || 960;
  74. let ratio = 1;
  75. if (Math.max(width, height) > limitSideLen) {
  76. ratio = limitSideLen / Math.max(width, height);
  77. this.logger.debug(`缩放比例: ${ratio.toFixed(4)}`);
  78. }
  79. const newWidth = Math.floor(width * ratio);
  80. const newHeight = Math.floor(height * ratio);
  81. // 确保尺寸是32的倍数
  82. const finalWidth = Math.max(32, Math.floor(newWidth / 32) * 32);
  83. const finalHeight = Math.max(32, Math.floor(newHeight / 32) * 32);
  84. this.logger.debug(`调整后尺寸: ${finalWidth}x${finalHeight}`);
  85. return { width: finalWidth, height: finalHeight };
  86. }
  87. async getImageInfo(imagePath) {
  88. try {
  89. const metadata = await sharp(imagePath).metadata();
  90. return {
  91. width: metadata.width || 0,
  92. height: metadata.height || 0,
  93. format: metadata.format || 'unknown',
  94. processed: false
  95. };
  96. } catch (error) {
  97. this.logger.error('获取图像信息失败', error);
  98. return {
  99. width: 0, height: 0, format: 'unknown', processed: false
  100. };
  101. }
  102. }
  103. }
  104. export default ImagePreprocessor;