textPostProcessor.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. // server/utils/textPostProcessor.js
  2. class TextPostProcessor {
  3. buildTextBlocks(recognitionResults) {
  4. if (!recognitionResults || recognitionResults.length === 0) {
  5. return [{
  6. type: 'text',
  7. content: '未识别到文本',
  8. confidence: 0
  9. }];
  10. }
  11. console.log(`📊 开始构建文本块,共 ${recognitionResults.length} 个识别结果`);
  12. const lines = this.groupTextIntoLines(recognitionResults);
  13. const blocks = [];
  14. for (const line of lines) {
  15. const content = line.map(item => item.text).join('');
  16. const avgConfidence = line.reduce((sum, item) => sum + item.confidence, 0) / line.length;
  17. const type = this.classifyTextType(content);
  18. blocks.push({
  19. type,
  20. content,
  21. confidence: avgConfidence,
  22. ...(type === 'citation' && { number: this.extractCitationNumber(content) })
  23. });
  24. console.log(`📝 文本行: "${content}" (${type}, 置信度: ${avgConfidence.toFixed(4)})`);
  25. }
  26. const mergedBlocks = this.mergeShortTextBlocks(blocks);
  27. console.log(`✅ 文本块构建完成: ${mergedBlocks.length} 个块`);
  28. return mergedBlocks;
  29. }
  30. groupTextIntoLines(results) {
  31. if (results.length === 0) return [];
  32. const lines = [];
  33. const sortedResults = [...results].sort((a, b) => a.box.y1 - b.box.y1);
  34. let currentLine = [];
  35. let currentY = -1;
  36. const lineThreshold = 0.8 * this.calculateAverageHeight(results);
  37. for (const result of sortedResults) {
  38. if (currentY === -1 || Math.abs(result.box.y1 - currentY) < lineThreshold) {
  39. currentLine.push(result);
  40. if (currentY === -1) currentY = result.box.y1;
  41. else currentY = (currentY + result.box.y1) / 2;
  42. } else {
  43. if (currentLine.length > 0) {
  44. currentLine.sort((a, b) => a.box.x1 - b.box.x1);
  45. lines.push(currentLine);
  46. }
  47. currentLine = [result];
  48. currentY = result.box.y1;
  49. }
  50. }
  51. if (currentLine.length > 0) {
  52. currentLine.sort((a, b) => a.box.x1 - b.box.x1);
  53. lines.push(currentLine);
  54. }
  55. return lines;
  56. }
  57. calculateAverageHeight(results) {
  58. if (results.length === 0) return 0;
  59. const totalHeight = results.reduce((sum, result) => {
  60. const height = Math.max(result.box.y1, result.box.y2, result.box.y3, result.box.y4) -
  61. Math.min(result.box.y1, result.box.y2, result.box.y3, result.box.y4);
  62. return sum + height;
  63. }, 0);
  64. return totalHeight / results.length;
  65. }
  66. classifyTextType(text) {
  67. if (this.isReference(text)) return 'reference';
  68. if (this.isCitation(text)) return 'citation';
  69. if (this.isImageMarker(text)) return 'image';
  70. if (this.isTableMarker(text)) return 'table';
  71. return 'text';
  72. }
  73. isReference(text) {
  74. const refPatterns = [
  75. /^参考文献/i,
  76. /^references/i,
  77. /^bibliography/i,
  78. /^引用文献/i,
  79. /^参考书目/i
  80. ];
  81. return refPatterns.some(pattern => pattern.test(text));
  82. }
  83. isCitation(text) {
  84. return /^\[\d+\]/.test(text);
  85. }
  86. extractCitationNumber(text) {
  87. const match = text.match(/^\[(\d+)\]/);
  88. return match ? parseInt(match[1]) : null;
  89. }
  90. isImageMarker(text) {
  91. const imagePatterns = [
  92. /^图\s*\d+/i,
  93. /^figure\s*\d+/i,
  94. /^图片\s*\d+/i,
  95. /^图表\s*\d+/i,
  96. /^fig\.?\s*\d+/i
  97. ];
  98. return imagePatterns.some(pattern => pattern.test(text));
  99. }
  100. isTableMarker(text) {
  101. const tablePatterns = [
  102. /^表\s*\d+/i,
  103. /^table\s*\d+/i,
  104. /^表格\s*\d+/i
  105. ];
  106. return tablePatterns.some(pattern => pattern.test(text));
  107. }
  108. mergeShortTextBlocks(blocks) {
  109. if (blocks.length <= 1) return blocks;
  110. const mergedBlocks = [];
  111. let currentBlock = { ...blocks[0] };
  112. for (let i = 1; i < blocks.length; i++) {
  113. const block = blocks[i];
  114. // 放宽合并条件
  115. if (currentBlock.type === 'text' &&
  116. block.type === 'text' &&
  117. currentBlock.content.length < 100) { // 增加长度限制
  118. currentBlock.content += ' ' + block.content;
  119. currentBlock.confidence = (currentBlock.confidence + block.confidence) / 2;
  120. } else {
  121. mergedBlocks.push(currentBlock);
  122. currentBlock = { ...block };
  123. }
  124. }
  125. mergedBlocks.push(currentBlock);
  126. return mergedBlocks;
  127. }
  128. calculateOverallConfidence(results) {
  129. if (results.length === 0) return 0;
  130. const total = results.reduce((sum, result) => sum + result.confidence, 0);
  131. return total / results.length;
  132. }
  133. }
  134. export default TextPostProcessor;