remove-background-base64.js 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. // Base64 图片抠图处理模块
  2. // 接收 base64 图片,返回抠图后的 base64 图片
  3. const { spawn } = require('child_process');
  4. const path = require('path');
  5. const fs = require('fs').promises;
  6. const fsSync = require('fs');
  7. const os = require('os');
  8. class RemoveBackgroundBase64 {
  9. /**
  10. * 处理 base64 图片抠图请求
  11. * @param {http.IncomingMessage} req - HTTP 请求对象
  12. * @param {http.ServerResponse} res - HTTP 响应对象
  13. */
  14. static async handleRequest(req, res) {
  15. if (req.method !== 'POST') {
  16. res.writeHead(405, { 'Content-Type': 'application/json' });
  17. res.end(JSON.stringify({ error: 'Method not allowed' }));
  18. return;
  19. }
  20. let body = '';
  21. req.on('data', (chunk) => {
  22. body += chunk.toString();
  23. });
  24. req.on('end', async () => {
  25. try {
  26. const data = JSON.parse(body);
  27. const { imageBase64 } = data;
  28. if (!imageBase64) {
  29. res.writeHead(400, { 'Content-Type': 'application/json' });
  30. res.end(JSON.stringify({ error: 'Missing required field: imageBase64' }));
  31. return;
  32. }
  33. // 调用抠图处理
  34. const result = await this.removeBackground(imageBase64);
  35. if (result.success) {
  36. res.writeHead(200, {
  37. 'Content-Type': 'application/json',
  38. 'Access-Control-Allow-Origin': '*'
  39. });
  40. res.end(JSON.stringify({
  41. success: true,
  42. imageData: result.imageData
  43. }));
  44. } else {
  45. res.writeHead(500, { 'Content-Type': 'application/json' });
  46. res.end(JSON.stringify({ error: result.error || '抠图失败' }));
  47. }
  48. } catch (error) {
  49. console.error('[RemoveBackgroundBase64] 处理请求失败:', error);
  50. res.writeHead(500, { 'Content-Type': 'application/json' });
  51. res.end(JSON.stringify({ error: '处理失败', details: error.message }));
  52. }
  53. });
  54. req.on('error', (error) => {
  55. console.error('[RemoveBackgroundBase64] 请求错误:', error);
  56. res.writeHead(500, { 'Content-Type': 'application/json' });
  57. res.end(JSON.stringify({ error: 'Request error', details: error.message }));
  58. });
  59. }
  60. /**
  61. * 对 base64 图片进行抠图
  62. * @param {string} imageBase64 - base64 图片数据
  63. * @returns {Promise<Object>} 处理结果
  64. */
  65. static async removeBackground(imageBase64) {
  66. const tempDir = os.tmpdir();
  67. const inputPath = path.join(tempDir, `input_${Date.now()}.png`);
  68. const outputPath = path.join(tempDir, `output_${Date.now()}.png`);
  69. try {
  70. // 将 base64 转换为图片文件
  71. const imageBuffer = Buffer.from(imageBase64, 'base64');
  72. await fs.writeFile(inputPath, imageBuffer);
  73. console.log('[RemoveBackgroundBase64] 临时文件已创建:', inputPath);
  74. // 调用 Python 脚本进行抠图
  75. const pythonScript = path.join(__dirname, 'python', 'rembg-matting.py');
  76. const result = await this.runPythonScript(pythonScript, inputPath, outputPath);
  77. if (!result.success) {
  78. return { success: false, error: result.error };
  79. }
  80. // 读取输出图片并转换为 base64
  81. const outputBuffer = await fs.readFile(outputPath);
  82. const outputBase64 = outputBuffer.toString('base64');
  83. // 清理临时文件
  84. await this.cleanupFiles([inputPath, outputPath]);
  85. console.log('[RemoveBackgroundBase64] ✓ 抠图完成');
  86. return { success: true, imageData: outputBase64 };
  87. } catch (error) {
  88. console.error('[RemoveBackgroundBase64] 抠图失败:', error);
  89. // 清理临时文件
  90. await this.cleanupFiles([inputPath, outputPath]).catch(() => {});
  91. return { success: false, error: error.message };
  92. }
  93. }
  94. /**
  95. * 运行 Python 脚本
  96. * @param {string} scriptPath - Python 脚本路径
  97. * @param {string} inputPath - 输入文件路径
  98. * @param {string} outputPath - 输出文件路径
  99. * @returns {Promise<Object>} 执行结果
  100. */
  101. static runPythonScript(scriptPath, inputPath, outputPath) {
  102. return new Promise((resolve) => {
  103. // 检查是否有虚拟环境
  104. const venvPython = path.join(__dirname, 'python', 'venv', 'Scripts', 'python.exe');
  105. let pythonCmd;
  106. // 优先使用虚拟环境中的 Python
  107. if (process.platform === 'win32' && fsSync.existsSync(venvPython)) {
  108. pythonCmd = venvPython;
  109. } else {
  110. pythonCmd = process.platform === 'win32' ? 'python' : 'python3';
  111. }
  112. const pythonProcess = spawn(pythonCmd, [scriptPath, inputPath, outputPath], {
  113. cwd: path.dirname(scriptPath)
  114. });
  115. let stdout = '';
  116. let stderr = '';
  117. pythonProcess.stdout.on('data', (data) => {
  118. stdout += data.toString();
  119. });
  120. pythonProcess.stderr.on('data', (data) => {
  121. stderr += data.toString();
  122. });
  123. pythonProcess.on('close', (code) => {
  124. if (code === 0) {
  125. resolve({ success: true });
  126. } else {
  127. resolve({ success: false, error: `Python脚本执行失败,退出码: ${code}\n${stderr}` });
  128. }
  129. });
  130. pythonProcess.on('error', (error) => {
  131. resolve({ success: false, error: `启动Python进程失败: ${error.message}` });
  132. });
  133. });
  134. }
  135. /**
  136. * 清理临时文件
  137. * @param {string[]} filePaths - 文件路径数组
  138. */
  139. static async cleanupFiles(filePaths) {
  140. for (const filePath of filePaths) {
  141. try {
  142. await fs.unlink(filePath);
  143. } catch (error) {
  144. // 忽略删除失败的错误
  145. }
  146. }
  147. }
  148. }
  149. module.exports = RemoveBackgroundBase64;