luoyutianyang commited on
Commit
1fb1e33
·
verified ·
1 Parent(s): 74690a1

Update egg-api.js

Browse files
Files changed (1) hide show
  1. egg-api.js +293 -132
egg-api.js CHANGED
@@ -1,153 +1,314 @@
1
  const express = require('express');
2
- const fs = require('fs');
3
- const path = require('path');
4
  const cv = require('opencv4nodejs');
5
  const Jimp = require('jimp');
6
  const axios = require('axios');
7
- const sharp = require('sharp');
 
 
 
8
  const serveStatic = require('serve-static');
9
 
10
  const app = express();
11
- const port = 7860;
12
 
13
  // 从文件夹加载所有分类器
14
- const loadClassifiersFromFolder = async (folderPath) => {
15
- const classifiers = [];
16
- const files = fs.readdirSync(folderPath);
17
- for (const file of files) {
18
- const filePath = path.join(folderPath, file);
19
- const classifier = new cv.CascadeClassifier(filePath);
20
- classifiers.push(classifier);
21
- }
22
- return classifiers;
23
- };
24
-
25
- // 下载并转换图像到内存中
26
- const downloadAndConvertImage = async (url) => {
27
- try {
28
- const response = await axios({
29
- method: 'get',
30
- url: url,
31
- responseType: 'arraybuffer'
32
- });
33
 
34
- const imageBuffer = await sharp(response.data)
35
- .toFormat('jpeg')
36
- .toBuffer();
37
-
38
- console.log('Image downloaded and converted successfully.');
39
- return imageBuffer;
40
- } catch (error) {
41
- console.error('Error downloading or converting image:', error);
42
- throw error;
43
- }
44
- };
45
-
46
- // 图像处理函数
47
- const processImage = async (imageBuffer, classifiersFolder, replacementImageBuffer) => {
48
- try {
49
- const classifiers = await loadClassifiersFromFolder(classifiersFolder);
50
-
51
- const jimpImage = await Jimp.read(imageBuffer);
52
- if (!jimpImage) {
53
- throw new Error('Failed to read the image with Jimp. The image file might be corrupted or not supported.');
54
- }
55
-
56
- const img = cv.imdecode(imageBuffer);
57
- if (img.empty) {
58
- throw new Error('Failed to read the image with OpenCV. The image file might be corrupted or not supported.');
59
  }
60
-
61
- const grayImg = img.bgrToGray();
62
- const allFaces = [];
63
- classifiers.forEach(classifier => {
64
- const faces = classifier.detectMultiScale(grayImg).objects;
65
- allFaces.push(...faces);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
  });
67
-
68
- // 如果没有检测到人脸,抛出错误
69
- if (allFaces.length === 0) {
70
- throw new Error('No faces detected in the image.');
71
- }
72
-
73
- const replacementFace = await Jimp.read(replacementImageBuffer);
74
- allFaces.forEach((faceRect, i) => {
75
- const resizedReplacementFace = replacementFace.resize(faceRect.width, faceRect.height);
76
- const faceRegion = img.getRegion(faceRect);
77
-
78
- const replacementBuffer = resizedReplacementFace.bitmap.data;
79
- const centerX = faceRect.width / 2;
80
- const centerY = faceRect.height / 2;
81
- const maxRadius = Math.min(centerX, centerY);
82
- for (let y = 0; y < faceRect.height; y++) {
83
- for (let x = 0; x < faceRect.width; x++) {
84
- const distance = Math.sqrt((x - centerX) ** 2 + (y - centerY) ** 2);
85
- if (distance <= maxRadius) {
86
- const idx = (y * faceRect.width + x) << 2;
87
- const [r, g, b, a] = replacementBuffer.slice(idx, idx + 4);
88
-
89
- if (a > 0) {
90
- faceRegion.set(y, x, new cv.Vec3(b, g, r));
91
- }
92
- }
93
- }
94
- }
95
  });
96
- const outputBuffer = cv.imencode('.jpg', img);
97
- return outputBuffer;
98
- } catch (error) {
99
- console.error('Error during image processing:', error);
100
- throw error;
101
- }
102
- };
103
-
104
- // Express接口
105
  app.get('/process-image', async (req, res) => {
106
- const { imageUrl, replacementImageUrl } = req.query;
107
-
108
- if (!imageUrl || !replacementImageUrl) {
109
- return res.status(400).send('Both image URL and replacement image URL are required');
110
- }
111
-
112
- const classifiersFolder = './classifiers';
113
- let replacementImageBuffer;
114
- try {
115
- // 下载并转换输入图片和替换图片到内存中
116
- const imageBuffer = await downloadAndConvertImage(imageUrl);
117
- if (replacementImageUrl === 'replace') {
118
- // 使用默认的本地替换图像路径
119
- const defaultReplacementImagePath = './replacement_face.png';
120
- replacementImageBuffer = await Jimp.read(defaultReplacementImagePath);
121
- } else {
122
- // 下载并转换替换图片到内存中
123
- replacementImageBuffer = await downloadAndConvertImage(replacementImageUrl);
124
- }
125
-
126
- // 处理图片
127
- const outputBuffer = await processImage(imageBuffer, classifiersFolder, replacementImageBuffer);
128
-
129
- // 返回处理后的图片
130
- res.set('Content-Type', 'image/jpeg');
131
- res.send(outputBuffer);
132
- } catch (error) {
133
- if (error.message === 'No faces detected in the image.') {
134
- res.status(404).send('No faces detected in the image.');
135
- } else {
136
- console.error('Error processing image:', error);
137
- res.status(500).send('Error processing image');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
138
  }
139
- }
140
  });
141
 
142
- // 记录使用次数和内存占用全局变量
143
- let usageCount = 0;
144
- let memoryUsage = 0;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
145
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
146
  app.use(serveStatic(__dirname)); // 添加这一行,设置静态文件服务器
147
  app.get('/', (req, res) => {
148
- res.sendFile(path.join(__dirname, 'index.html')); // 修改这一行,发送HTML文件
 
 
 
 
149
  });
150
-
151
- app.listen(port, () => {
152
- console.log(`Server is running on http://localhost:${port}`);
153
- });
 
1
  const express = require('express');
 
 
2
  const cv = require('opencv4nodejs');
3
  const Jimp = require('jimp');
4
  const axios = require('axios');
5
+ const fs = require('fs');
6
+ const os = require('os');
7
+ const path = require('path');
8
+ const ffmpeg = require('fluent-ffmpeg');
9
  const serveStatic = require('serve-static');
10
 
11
  const app = express();
12
+ const PORT = 7860;
13
 
14
  // 从文件夹加载所有分类器
15
+ async function loadClassifiersFromFolder(folderPath) {
16
+ console.log(`开始从文件夹 ${folderPath} 加载分类器...`);
17
+ const classifiers = [];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
 
19
+ const files = fs.readdirSync(folderPath);
20
+ for (const file of files) {
21
+ const filePath = path.join(folderPath, file);
22
+ const classifier = new cv.CascadeClassifier(filePath);
23
+ classifiers.push(classifier);
24
+ console.log(`加载分类器: ${file}`);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  }
26
+
27
+ console.log('分类器加载完成');
28
+ return classifiers;
29
+ }
30
+
31
+ // GIF 转换为视频
32
+ async function gifToVideo(inputGifPath, outputVideoPath) {
33
+ console.log('开始将GIF转换为视频...');
34
+ return new Promise((resolve, reject) => {
35
+ ffmpeg(inputGifPath)
36
+ .inputFormat('gif')
37
+ .output(outputVideoPath)
38
+ .on('start', (commandLine) => {
39
+ console.log('FFmpeg 命令:', commandLine);
40
+ })
41
+ .on('end', () => {
42
+ console.log('GIF成功转换为视频:', outputVideoPath);
43
+ resolve();
44
+ })
45
+ .on('error', (err) => {
46
+ console.error('GIF转换为视频时出错:', err);
47
+ reject(err);
48
+ })
49
+ .run();
50
  });
51
+ }
52
+
53
+ // 视频转换为 GIF
54
+ async function videoToGif(inputVideoPath, outputGifPath) {
55
+ console.log('开始将视频转换为GIF...');
56
+ return new Promise((resolve, reject) => {
57
+ ffmpeg(inputVideoPath)
58
+ .output(outputGifPath)
59
+ .on('start', (commandLine) => {
60
+ console.log('FFmpeg 命令:', commandLine);
61
+ })
62
+ .on('end', () => {
63
+ console.log('视频成功转换为GIF:', outputGifPath);
64
+ resolve();
65
+ })
66
+ .on('error', (err) => {
67
+ console.error('视频转换为GIF时出错:', err);
68
+ reject(err);
69
+ })
70
+ .run();
 
 
 
 
 
 
 
 
71
  });
72
+ }
73
+ // 下载并转换图片到内存的辅助函数
74
+ async function downloadAndConvertImage(url) {
75
+ const response = await axios.get(url, { responseType: 'arraybuffer' });
76
+ return await Jimp.read(response.data);
77
+ }
78
+
79
+ // 处理图片的 Express 接口
 
80
  app.get('/process-image', async (req, res) => {
81
+ try {
82
+ const { imageUrl, replacementImageUrl } = req.query;
83
+ const tempDir = os.tmpdir();
84
+ const tempImagePath = path.join(tempDir, 'temp_image.jpg');
85
+ const tempImagePaths = path.join(tempDir, 'temp_images.jpg');
86
+ const outputImagePath = path.join(tempDir, 'output_image.jpg');
87
+ const classifiersFolder = path.join(__dirname, 'classifiers');
88
+
89
+ console.log('接收到图片处理请求:', imageUrl);
90
+
91
+ // 下载图片到本地
92
+ console.log('开始下载图片...');
93
+ const response = await axios.get(imageUrl, { responseType: 'stream' });
94
+ const writer = fs.createWriteStream(tempImagePath);
95
+ response.data.pipe(writer);
96
+
97
+ // 等待文件写入完成
98
+ await new Promise((resolve, reject) => {
99
+ writer.on('finish', resolve);
100
+ writer.on('error', reject);
101
+ });
102
+
103
+ let replacementImageBuffer;
104
+ if (replacementImageUrl === 'replace') {
105
+ // 使用默认的本地替换图像路径
106
+ const defaultReplacementImagePath = './replacement_face.png';
107
+ replacementImageBuffer = await Jimp.read(defaultReplacementImagePath);
108
+ } else {
109
+ // 下载并转换替换图片到内存中
110
+ const response = await axios.get(replacementImageUrl, { responseType: 'stream' });
111
+ const writer = fs.createWriteStream(tempImagePaths);
112
+ response.data.pipe(writer);
113
+
114
+ // 等待文件写入完成
115
+ await new Promise((resolve, reject) => {
116
+ writer.on('finish', resolve);
117
+ writer.on('error', reject);
118
+ });
119
+
120
+ replacementImageBuffer = await Jimp.read(tempImagePath)
121
+ }
122
+ console.log('图片下载完成:', tempImagePath);
123
+
124
+ // 从文件夹动态加载分类器
125
+ const classifiers = await loadClassifiersFromFolder(classifiersFolder);
126
+
127
+ // 读取图片
128
+ console.log('开始读取图片...');
129
+ const image = await cv.imreadAsync(tempImagePath);
130
+ const grayImage = image.bgrToGray();
131
+
132
+ // 检测人脸
133
+ console.log('开始检测人脸...');
134
+ const faces = [];
135
+ classifiers.forEach(classifier => {
136
+ const allFaces = classifier.detectMultiScale(grayImage).objects;
137
+ faces.push(...allFaces);
138
+ });
139
+
140
+
141
+
142
+ const replacementFace = replacementImageBuffer;
143
+
144
+ if (Array.isArray(faces) && faces.length > 0) {
145
+ console.log(`检测到 ${faces.length} 张脸`);
146
+ for (const rect of faces) {
147
+ const resizedSubstituteImage = await replacementFace.clone().resize(rect.width, rect.height);
148
+ const substituteRegion = image.getRegion(rect);
149
+ for (let y = 0; y < rect.height; y++) {
150
+ for (let x = 0; x < rect.width; x++) {
151
+ const { r, g, b, a } = Jimp.intToRGBA(resizedSubstituteImage.getPixelColor(x, y));
152
+ if (a > 0) {
153
+ const color = new cv.Vec3(b, g, r);
154
+ substituteRegion.set(y, x, color);
155
+ }
156
+ }
157
+ }
158
+ }
159
+ } else {
160
+ console.error('未检测到人脸或faces数组为空');
161
+ }
162
+
163
+ // 保存处理后的图片
164
+ await cv.imwriteAsync(outputImagePath, image);
165
+ console.log('图片处理完成并保存:', outputImagePath);
166
+
167
+ // 发送处理后的图片
168
+ res.set('Content-Type', 'image/jpeg');
169
+ res.sendFile(outputImagePath, async (err) => {
170
+ if (err) {
171
+ console.error('发送文件时出错:', err);
172
+ res.status(500).send('内部服务器错误');
173
+ } else {
174
+ // 删除临时文件
175
+ fs.unlinkSync(tempImagePath);
176
+ fs.unlinkSync(tempImagePaths);
177
+ fs.unlinkSync(outputImagePath);
178
+ }
179
+ });
180
+ } catch (error) {
181
+ console.error('处理图片时出错:', error);
182
+ res.status(500).send('内部服务器错误');
183
  }
 
184
  });
185
 
186
+ // 处理 GIF Express 接口
187
+ app.get('/processGif', async (req, res) => {
188
+ try {
189
+ const { gifUrl, replacementImageUrl } = req.query;
190
+ const tempDir = os.tmpdir();
191
+ const tempGifPath = path.join(tempDir, 'temp.gif');
192
+ const tempGifPaths = path.join(tempDir, 'temps.gif');
193
+ const tempVideoPath = path.join(tempDir, 'temp_video.mp4');
194
+ const outputGifPath = path.join(tempDir, 'output.gif');
195
+ const classifiersFolder = path.join(__dirname, 'classifiers');
196
+
197
+ console.log('接收到GIF处理请求:', gifUrl);
198
+
199
+ // 下载 GIF 到本地
200
+ console.log('开始下载GIF...');
201
+ const response = await axios.get(gifUrl, { responseType: 'stream' });
202
+ const writer = fs.createWriteStream(tempGifPath);
203
+ response.data.pipe(writer);
204
+
205
+ // 等待文件写入完成
206
+ await new Promise((resolve, reject) => {
207
+ writer.on('finish', resolve);
208
+ writer.on('error', reject);
209
+ });
210
+
211
+ let replacementImageBuffer = tempGifPaths
212
+ if (replacementImageUrl === 'replace') {
213
+ // 使用默认的本地替换图像路径
214
+ const defaultReplacementImagePath = './replacement_face.png';
215
+ replacementImageBuffer = await Jimp.read(defaultReplacementImagePath);
216
+ } else {
217
+ // 下载并转换替换图片到内存中
218
+ const response = await axios.get(replacementImageUrl, { responseType: 'stream' });
219
+ const writer = fs.createWriteStream(tempGifPaths);
220
+ response.data.pipe(writer);
221
+ // 等待文件写入完成
222
+ await new Promise((resolve, reject) => {
223
+ writer.on('finish', resolve);
224
+ writer.on('error', reject);
225
+ });
226
+ replacementImageBuffer=await Jimp.read(tempGifPaths)
227
+ }
228
+ console.log('GIF下载完成:', tempGifPath);
229
+
230
+ // 从文件夹动态加载分类器
231
+ const classifiers = await loadClassifiersFromFolder(classifiersFolder);
232
+
233
+ // 将GIF转换为视频
234
+ await gifToVideo(tempGifPath, tempVideoPath);
235
+
236
+ // 读取视频文件
237
+ console.log('开始读取视频文件...');
238
+ const videoCapture = new cv.VideoCapture(tempVideoPath);
239
+ const frameWidth = videoCapture.get(cv.CAP_PROP_FRAME_WIDTH);
240
+ const frameHeight = videoCapture.get(cv.CAP_PROP_FRAME_HEIGHT);
241
+ const originalFps = videoCapture.get(cv.CAP_PROP_FPS);
242
+ const videoWriter = new cv.VideoWriter(path.join(tempDir, 'out.mp4'), cv.VideoWriter.fourcc('avc1'), originalFps, new cv.Size(frameWidth, frameHeight));
243
+ console.log('视频文件读取完成');
244
+
245
+ // 处理每一帧
246
+ console.log('开始处理每一帧...');
247
+ while (true) {
248
+ const frame = videoCapture.read();
249
+ if (frame.empty) break;
250
 
251
+ const grayFrame = frame.bgrToGray();
252
+ const faces = [];
253
+ classifiers.forEach(classifier => {
254
+ const allfaces = classifier.detectMultiScale(grayFrame).objects;
255
+ faces.push(...allfaces);
256
+ });
257
+
258
+ const replacementFace = replacementImageBuffer;
259
+
260
+ if (Array.isArray(faces) && faces.length > 0) {
261
+ console.log(`检测到 ${faces.length} 张脸`);
262
+ for (const rect of faces) {
263
+ const resizedSubstituteImage = await replacementFace.clone().resize(rect.width, rect.height);
264
+ const substituteRegion = frame.getRegion(rect);
265
+ for (let y = 0; y < rect.height; y++) {
266
+ for (let x = 0; x < rect.width; x++) {
267
+ const { r, g, b, a } = Jimp.intToRGBA(resizedSubstituteImage.getPixelColor(x, y));
268
+ if (a > 0) {
269
+ const color = new cv.Vec3(b, g, r);
270
+ substituteRegion.set(y, x, color);
271
+ }
272
+ }
273
+ }
274
+ }
275
+ } else {
276
+ console.error('未检测到人脸或faces数组为空');
277
+ }
278
+
279
+ videoWriter.write(frame);
280
+ }
281
+
282
+ videoCapture.release();
283
+ videoWriter.release();
284
+ cv.destroyAllWindows();
285
+ console.log('帧处理完成');
286
+
287
+ await videoToGif(path.join(tempDir, 'out.mp4'), outputGifPath);
288
+
289
+ res.set('Content-Type', 'image/gif');
290
+ res.sendFile(outputGifPath, async (err) => {
291
+ if (err) {
292
+ console.error('发送GIF文件时出错:', err);
293
+ res.status(500).send('内部服务器错误');
294
+ } else {
295
+ // 删除临时文件
296
+ fs.unlinkSync(tempGifPath);
297
+ fs.unlinkSync(tempGifPaths);
298
+ fs.unlinkSync(tempVideoPath);
299
+ fs.unlinkSync(outputGifPath);
300
+ }
301
+ });
302
+ } catch (error) {
303
+ console.error('处理GIF时出错:', error);
304
+ res.status(500).send('内部服务器错误');
305
+ }
306
+ });
307
  app.use(serveStatic(__dirname)); // 添加这一行,设置静态文件服务器
308
  app.get('/', (req, res) => {
309
+ res.sendFile(path.join(__dirname, 'index.html')); // 修改这一行,发送HTML文件
310
+ });
311
+ // 启动 Express 服务器
312
+ app.listen(PORT, () => {
313
+ console.log(`服务器运行在端口 ${PORT}`);
314
  });