File size: 6,455 Bytes
645e6ce
 
 
1b1f7c9
645e6ce
1b1f7c9
22fefdc
 
645e6ce
cd2f3db
1b1f7c9
 
645e6ce
cd2f3db
d715e46
cd2f3db
9f4a0b0
d715e46
1b1f7c9
d715e46
9f4a0b0
 
 
cd2f3db
ceb1c28
cd2f3db
 
 
 
 
 
1b1f7c9
4d6d7ae
645e6ce
 
1b1f7c9
 
cd2f3db
9f4a0b0
 
1b1f7c9
9f4a0b0
 
 
cd2f3db
ceb1c28
cd2f3db
 
 
 
 
 
 
1b1f7c9
 
4d6d7ae
1b1f7c9
 
cd2f3db
 
1b1f7c9
cd2f3db
1b1f7c9
d715e46
cd2f3db
 
 
4d6d7ae
 
cd2f3db
 
1b1f7c9
645e6ce
1b1f7c9
 
 
 
22fefdc
3186dbd
8f710ba
3186dbd
22fefdc
 
1b1f7c9
cd2f3db
 
 
 
d715e46
 
 
 
 
 
 
 
 
cd2f3db
ceb1c28
9f4a0b0
1b1f7c9
 
 
 
d715e46
1b1f7c9
a3e9728
 
 
 
 
 
 
 
 
ceb1c28
1b1f7c9
cd2f3db
1b1f7c9
 
 
 
 
 
 
 
 
 
 
 
 
 
cd2f3db
1b1f7c9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
cd2f3db
1b1f7c9
 
 
645e6ce
4d6d7ae
1b1f7c9
 
 
cd2f3db
 
3186dbd
645e6ce
1b1f7c9
22fefdc
1b1f7c9
cd2f3db
 
1b1f7c9
645e6ce
 
084bd67
1b1f7c9
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
const express = require('express');
const cv = require('opencv4nodejs');
const Jimp = require('jimp');
const ffmpeg = require('fluent-ffmpeg');
const axios = require('axios');
const fs = require('fs');
const os = require('os');
const path = require('path');
const app = express();
const PORT = 7860;

app.use(express.json());

// 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();
    });
}

// 从文件夹加载所有分类器
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 的 Express 接口
app.get('/processGif', async (req, res) => {
    try {
        const { gifUrl } = req.query;
        const tempDir = os.tmpdir();
        const tempGifPath = path.join(tempDir, 'temp.gif');
        const tempVideoPath = path.join(tempDir, 'temp_video.mp4');
        const outputGifPath = path.join(tempDir, 'output.gif');
        const replacementImagePath = path.join(__dirname, 'replacement_face.png');
        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);
        });

        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 = await Jimp.read(replacementImagePath);

            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);
                            }
                        }
                    }
                    const buffer = await resizedSubstituteImage.getBufferAsync(Jimp.MIME_PNG);
                    const substituteMat = cv.imdecode(Buffer.from(buffer), cv.IMREAD_UNCHANGED);
                    substituteMat.copyTo(frame.getRegion(rect));
                }
            } else {
                console.error('未检测到人脸或faces数组为空');
            }

            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);
    } catch (error) {
        console.error('处理GIF时出错:', error);
        res.status(500).send('内部服务器错误');
    }
});


app.listen(PORT, () => {
    console.log(`服务器运行在端口 ${PORT}`);
});