File size: 12,203 Bytes
645e6ce
 
 
 
28ecdaa
 
 
d9ae3dc
ab8d53d
645e6ce
ab8d53d
28ecdaa
d9ae3dc
1b1f7c9
d9ae3dc
 
 
28ecdaa
d9ae3dc
 
 
 
 
 
1b1f7c9
645e6ce
d9ae3dc
 
 
28ecdaa
d9ae3dc
28ecdaa
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d9ae3dc
28ecdaa
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d9ae3dc
 
 
f25dce2
d9ae3dc
 
f25dce2
d9ae3dc
 
28ecdaa
d9ae3dc
28ecdaa
d9ae3dc
 
 
 
 
 
 
 
 
 
 
 
f25dce2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5bb1be6
f25dce2
d9ae3dc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c53dc08
f25dce2
 
d9ae3dc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e027443
d9ae3dc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
055ed5f
a1da4bc
 
d9ae3dc
 
 
 
 
 
 
 
28ecdaa
 
 
 
c53dc08
28ecdaa
 
f25dce2
28ecdaa
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c53dc08
f25dce2
 
 
 
 
 
 
 
 
 
 
 
 
 
a1da4bc
f25dce2
28ecdaa
 
 
 
 
 
 
 
d9ae3dc
28ecdaa
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c53dc08
28ecdaa
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e027443
28ecdaa
 
 
 
 
 
 
 
 
 
 
 
 
d9ae3dc
 
 
 
 
 
 
055ed5f
 
a1da4bc
d9ae3dc
 
 
 
28ecdaa
 
 
 
 
d9ae3dc
 
f25dce2
d9ae3dc
 
28ecdaa
 
cd2f3db
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
const express = require('express');
const cv = require('opencv4nodejs');
const Jimp = require('jimp');
const axios = require('axios');
const fs = require('fs');
const os = require('os');
const path = require('path');
const ffmpeg = require('fluent-ffmpeg');
const serveStatic = require('serve-static');

const app = express();
const PORT = 7860;

// 从文件夹加载所有分类器
async function loadClassifiersFromFolder(folderPath) {
    console.log(`开始从文件夹 ${folderPath} 加载分类器...`);
    const classifiers = [];

    const files = fs.readdirSync(folderPath);
    for (const file of files) {
        const filePath = path.join(folderPath, file);
        const classifier = new cv.CascadeClassifier(filePath);
        classifiers.push(classifier);
        console.log(`加载分类器: ${file}`);
    }

    console.log('分类器加载完成');
    return classifiers;
}

// GIF 转换为视频
async function gifToVideo(inputGifPath, outputVideoPath) {
    console.log('开始将GIF转换为视频...');
    return new Promise((resolve, reject) => {
        ffmpeg(inputGifPath)
            .inputFormat('gif')
            .output(outputVideoPath)
            .on('start', (commandLine) => {
                console.log('FFmpeg 命令:', commandLine);
            })
            .on('end', () => {
                console.log('GIF成功转换为视频:', outputVideoPath);
                resolve();
            })
            .on('error', (err) => {
                console.error('GIF转换为视频时出错:', err);
                reject(err);
            })
            .run();
    });
}

// 视频转换为 GIF
async function videoToGif(inputVideoPath, outputGifPath) {
    console.log('开始将视频转换为GIF...');
    return new Promise((resolve, reject) => {
        ffmpeg(inputVideoPath)
            .output(outputGifPath)
            .on('start', (commandLine) => {
                console.log('FFmpeg 命令:', commandLine);
            })
            .on('end', () => {
                console.log('视频成功转换为GIF:', outputGifPath);
                resolve();
            })
            .on('error', (err) => {
                console.error('视频转换为GIF时出错:', err);
                reject(err);
            })
            .run();
    });
}

// 处理图片的 Express 接口
app.get('/process-image', async (req, res) => {
    try {
        const { imageUrl, replacementImageUrl } = req.query;
        const tempDir = os.tmpdir();
        const tempImagePath = path.join(tempDir, 'temp_image.jpg');
        const tempImagePaths = path.join(tempDir, 'temp_images.jpg');
        const outputImagePath = path.join(tempDir, 'output_image.jpg');
        const classifiersFolder = path.join(__dirname, 'classifiers');

        console.log('接收到图片处理请求:', imageUrl);

        // 下载图片到本地
        console.log('开始下载图片...');
        const response = await axios.get(imageUrl, { responseType: 'stream' });
        const writer = fs.createWriteStream(tempImagePath);
        response.data.pipe(writer);

        // 等待文件写入完成
        await new Promise((resolve, reject) => {
            writer.on('finish', resolve);
            writer.on('error', reject);
        });

        let replacementImageBuffer;
        if (replacementImageUrl === 'replace') {
            // 使用默认的本地替换图像路径
            const defaultReplacementImagePath = './replacement_face.png';
            replacementImageBuffer = await Jimp.read(defaultReplacementImagePath);
        } else {
            // 下载并转换替换图片到内存中
            const response = await axios.get(replacementImageUrl, { responseType: 'stream' });
            const writer = fs.createWriteStream(tempImagePaths);
            response.data.pipe(writer);

            // 等待文件写入完成
            await new Promise((resolve, reject) => {
                writer.on('finish', resolve);
                writer.on('error', reject);
            });

            replacementImageBuffer = await Jimp.read(tempImagePaths)
        }
        console.log('图片下载完成:', tempImagePath);

        // 从文件夹动态加载分类器
        const classifiers = await loadClassifiersFromFolder(classifiersFolder);

        // 读取图片
        console.log('开始读取图片...');
        const image = await cv.imreadAsync(tempImagePath);
        const grayImage = image.bgrToGray();

        // 检测人脸
        console.log('开始检测人脸...');
        const faces = [];
        classifiers.forEach(classifier => {
            const allFaces = classifier.detectMultiScale(grayImage).objects;
            faces.push(...allFaces);
        });



        const replacementFace = replacementImageBuffer;

        if (Array.isArray(faces) && faces.length > 0) {
            console.log(`检测到 ${faces.length} 张脸`);
            for (const rect of faces) {
                const resizedSubstituteImage = await replacementFace.clone().resize(rect.width, rect.height);
                const substituteRegion = image.getRegion(rect);
                for (let y = 0; y < rect.height; y++) {
                    for (let x = 0; x < rect.width; x++) {
                        const { r, g, b, a } = Jimp.intToRGBA(resizedSubstituteImage.getPixelColor(x, y));
                        if (a > 0) {
                            const color = new cv.Vec3(b, g, r);
                            substituteRegion.set(y, x, color);
                        }
                    }
                }
            }
        } else {
            console.error('未检测到人脸或faces数组为空');
            throw new Error('No faces detected in the image.');
        }

        // 保存处理后的图片
        await cv.imwriteAsync(outputImagePath, image);
        console.log('图片处理完成并保存:', outputImagePath);

        // 发送处理后的图片
        res.set('Content-Type', 'image/jpeg');
        res.sendFile(outputImagePath, async (err) => {
            if (err) {
                console.error('发送文件时出错:', err);
                res.status(500).send('内部服务器错误');
            } else {
                // 删除临时文件
                fs.unlinkSync(tempImagePath);
                if (fs.existsSync(tempImagePaths)) {
                    fs.unlinkSync(tempImagePaths);
                }
                fs.unlinkSync(outputImagePath);
            }
        });
    } catch (error) {
        console.error('处理图片时出错:', error);
        res.status(500).send('内部服务器错误');
    }
});

// 处理 GIF 的 Express 接口
app.get('/processGif', async (req, res) => {
    try {
        const { gifUrl, replacementImageUrl } = req.query;
        const tempDir = os.tmpdir();
        const tempGifPath = path.join(tempDir, 'temp.gif');
        const tempGifPaths = path.join(tempDir, 'temps.gif');
        const tempVideoPath = path.join(tempDir, 'temp_video.mp4');
        const outputGifPath = path.join(tempDir, 'output.gif');
        const classifiersFolder = path.join(__dirname, 'classifiers');

        console.log('接收到GIF处理请求:', gifUrl);

        // 下载 GIF 到本地
        console.log('开始下载GIF...');
        const response = await axios.get(gifUrl, { responseType: 'stream' });
        const writer = fs.createWriteStream(tempGifPath);
        response.data.pipe(writer);

        // 等待文件写入完成
        await new Promise((resolve, reject) => {
            writer.on('finish', resolve);
            writer.on('error', reject);
        });

        let replacementImageBuffer = tempGifPaths
        if (replacementImageUrl === 'replace') {
            // 使用默认的本地替换图像路径
            const defaultReplacementImagePath = './replacement_face.png';
            replacementImageBuffer = await Jimp.read(defaultReplacementImagePath);
        } else {
            // 下载并转换替换图片到内存中
            const response = await axios.get(replacementImageUrl, { responseType: 'stream' });
            const writer = fs.createWriteStream(tempGifPaths);
            response.data.pipe(writer);
            // 等待文件写入完成
            await new Promise((resolve, reject) => {
                writer.on('finish', resolve);
                writer.on('error', reject);
            });
            replacementImageBuffer = await Jimp.read(tempGifPaths)
        }
        console.log('GIF下载完成:', tempGifPath);

        // 从文件夹动态加载分类器
        const classifiers = await loadClassifiersFromFolder(classifiersFolder);

        // 将GIF转换为视频
        await gifToVideo(tempGifPath, tempVideoPath);

        // 读取视频文件
        console.log('开始读取视频文件...');
        const videoCapture = new cv.VideoCapture(tempVideoPath);
        const frameWidth = videoCapture.get(cv.CAP_PROP_FRAME_WIDTH);
        const frameHeight = videoCapture.get(cv.CAP_PROP_FRAME_HEIGHT);
        const originalFps = videoCapture.get(cv.CAP_PROP_FPS);
        const videoWriter = new cv.VideoWriter(path.join(tempDir, 'out.mp4'), cv.VideoWriter.fourcc('avc1'), originalFps, new cv.Size(frameWidth, frameHeight));
        console.log('视频文件读取完成');

        // 处理每一帧
        console.log('开始处理每一帧...');
        while (true) {
            const frame = videoCapture.read();
            if (frame.empty) break;

            const grayFrame = frame.bgrToGray();
            const faces = [];
            classifiers.forEach(classifier => {
                const allfaces = classifier.detectMultiScale(grayFrame).objects;
                faces.push(...allfaces);
            });

            const replacementFace = replacementImageBuffer;

            if (Array.isArray(faces) && faces.length > 0) {
                console.log(`检测到 ${faces.length} 张脸`);
                for (const rect of faces) {
                    const resizedSubstituteImage = await replacementFace.clone().resize(rect.width, rect.height);
                    const substituteRegion = frame.getRegion(rect);
                    for (let y = 0; y < rect.height; y++) {
                        for (let x = 0; x < rect.width; x++) {
                            const { r, g, b, a } = Jimp.intToRGBA(resizedSubstituteImage.getPixelColor(x, y));
                            if (a > 0) {
                                const color = new cv.Vec3(b, g, r);
                                substituteRegion.set(y, x, color);
                            }
                        }
                    }
                }
            } else {
                console.error('未检测到人脸或faces数组为空');
                throw new Error('No faces detected in the image.');
            }

            videoWriter.write(frame);
        }

        videoCapture.release();
        videoWriter.release();
        cv.destroyAllWindows();
        console.log('帧处理完成');

        await videoToGif(path.join(tempDir, 'out.mp4'), outputGifPath);

        res.set('Content-Type', 'image/gif');
        res.sendFile(outputGifPath, async (err) => {
            if (err) {
                console.error('发送GIF文件时出错:', err);
                res.status(500).send('内部服务器错误');
            } else {
                // 删除临时文件
                fs.unlinkSync(tempGifPath);
                if (fs.existsSync(tempGifPaths)) {
                    fs.unlinkSync(tempGifPaths);
                }
                fs.unlinkSync(tempVideoPath);
                fs.unlinkSync(outputGifPath);
            }
        });
    } catch (error) {
        console.error('处理GIF时出错:', error);
        res.status(500).send('内部服务器错误');
    }
});
app.use(serveStatic(__dirname)); // 添加这一行,设置静态文件服务器
app.get('/', (req, res) => {
    res.sendFile(path.join(__dirname, 'index.html')); // 修改这一行,发送HTML文件
});
// 启动 Express 服务器
app.listen(PORT, () => {
    console.log(`服务器运行在端口 ${PORT}`);
});