CoolFace
Apppublic

nchdlhbctm/TraceDetect-AI

sourceHugging Faceapache-2.0updated 4mo agoView on Hugging Face
0likes
app.py367 linesDownload Raw Back to root
1import streamlit as st
2import os
3import time
4from PIL import Image
5
6# 全局缓存 Whisper 语音大模型
7@st.cache_resource
8def load_whisper_model():
9    import whisper
10    return whisper.load_model("small")
11
12# 页面配置
13st.set_page_config(
14    page_title="多模态AI生成痕迹鉴别系统",
15    page_icon="🔍",
16    layout="wide"
17)
18
19# ==========================================
20# 侧边栏:系统设置与说明
21# ==========================================
22with st.sidebar:
23    st.title("⚙️ 鉴别引擎设置")
24    st.markdown("提供轻量、可解释的多模态AI生成内容快速鉴别能力。")
25    st.divider()
26
27    st.header("🎚️ 动态判定阈值")
28    st.markdown("调整敏感度,适应不同审核场景:")
29    high_risk_threshold = st.slider(
30        "高危报警阈值",
31        min_value=0.60, max_value=0.95, value=0.80, step=0.05,
32        help="高于此值判定为“高度疑似AI生成”"
33    )
34    warning_threshold = st.slider(
35        "存疑缓冲阈值",
36        min_value=0.20, max_value=0.55, value=0.40, step=0.05,
37        help="介于警告阈值与报警阈值之间时,标记为“存疑”"
38    )
39
40    st.divider()
41    st.caption("🔹 引擎状态:轻量模型已缓存 · 本地推理")
42
43# ==========================================
44# 主界面标题
45# ==========================================
46st.title("🔍 多模态AI生成痕迹快速鉴别系统")
47st.markdown(
48    "同时支持**图像、文本、视频**单模态扫描,以及**图文/视文**联合跨模态融合鉴定。"
49)
50
51work_mode = st.radio(
52    "请选择检测模式:",
53    ["单模态独立检测(图片 / 文本 / 视频)", "多模态联合检测(图片+文案 / 视频+文案)"],
54    horizontal=True
55)
56
57st.divider()
58
59# ==========================================
60# 模式一:单模态独立检测
61# ==========================================
62if "单模态独立检测" in work_mode:
63    with st.container(border=True):
64        uploaded_file = st.file_uploader(
65            "拖拽或点击上传待检测文件",
66            type=["jpg", "png", "jpeg", "txt", "docx", "mp4", "avi", "mov"],
67            help="支持常见图片、文本、视频格式,单文件≤50MB,视频时长建议≤5分钟"
68        )
69
70    if uploaded_file is not None:
71        file_type = uploaded_file.name.split('.')[-1].lower()
72        st.success(f"✅ 已接收:**{uploaded_file.name}** | 启动鉴别引擎...")
73
74        col_content, col_result = st.columns([1, 1.2], gap="large")
75
76        # ----------------- 图像检测 -----------------
77        if file_type in ['jpg', 'png', 'jpeg']:
78            with col_content:
79                st.subheader("原始图像")
80                st.image(uploaded_file, use_container_width=True)
81
82                st.subheader("AI痕迹热力图(Grad-CAM)")
83                heatmap_placeholder = st.empty()
84
85            with col_result:
86                st.subheader("⚙️ 特征提取中...")
87                progress_bar = st.progress(0)
88                status_text = st.empty()
89
90                for percent_complete in range(100):
91                    time.sleep(0.01)
92                    progress_bar.progress(percent_complete + 1)
93                    status_text.text(f"正在提取LBP纹理、频域伪影及深度语义... {percent_complete + 1}%")
94
95                from image_module import analyze_image, load_deep_image_model, generate_image_heatmap
96
97                result = analyze_image(uploaded_file)
98
99                model, device = load_deep_image_model()
100                # 图像模块内部我们已经处理过 seek(0),所以这里可以直接传
101                heatmap_img = generate_image_heatmap(uploaded_file, model, device)
102                heatmap_placeholder.image(
103                    heatmap_img,
104                    caption="🔴 红色区域为模型判定的高可疑伪造区域",
105                    use_container_width=True
106                )
107
108                status_text.text("特征提取完成")
109
110                st.subheader("检测报告")
111                final_prob = result['final_probability']
112
113                if final_prob >= high_risk_threshold:
114                    st.error(f"高度疑似AI生成图像(生成概率:{final_prob * 100:.1f}%)")
115                elif warning_threshold <= final_prob < high_risk_threshold:
116                    st.warning(f"可疑图像,可能经过AI修图或局部生成(概率:{final_prob * 100:.1f}%)")
117                else:
118                    st.success(f"真实图像,未检出明显生成痕迹(概率:{final_prob * 100:.1f}%)")
119
120                st.progress(float(final_prob))
121                with st.expander("展开底层特征参数", expanded=True):
122                    st.info(
123                        f"**双轨特征得分:**\n\n"
124                        f"- 传统物理特征异常度:{result['traditional_score'] * 100:.1f}%\n"
125                        f"- MobileNetV2 深度特征:{result['deep_score'] * 100:.1f}%"
126                    )
127
128        # ----------------- 文本检测 -----------------
129        elif file_type in ['txt', 'docx']:
130            text_content = ""
131            uploaded_file.seek(0) # 核心修复:倒带文件指针
132            if file_type == 'txt':
133                text_content = uploaded_file.read().decode("utf-8")
134            elif file_type == 'docx':
135                import docx
136                doc = docx.Document(uploaded_file)
137                text_content = "\n".join([para.text for para in doc.paragraphs])
138
139            with col_content:
140                st.subheader("原始文本内容")
141                text_placeholder = st.empty()
142                text_placeholder.text_area("提取的文本", text_content, height=350)
143
144            with col_result:
145                st.subheader("⚙️ 语义连贯性分析中...")
146                with st.spinner("正在计算困惑度、句法复杂度及BERT深度特征..."):
147                    from text_module import analyze_text, get_custom_text_model, generate_text_highlight_html
148
149                    result = analyze_text(text_content)
150
151                    tokenizer, model = get_custom_text_model()
152                    highlighted_html = generate_text_highlight_html(text_content, tokenizer, model)
153                    text_placeholder.markdown("**🔥 AI痕迹逐句高亮(黄色为高风险句式)**", unsafe_allow_html=True)
154                    text_placeholder.markdown(highlighted_html, unsafe_allow_html=True)
155
156                st.subheader("检测报告")
157                final_prob = result['final_probability']
158
159                if final_prob >= high_risk_threshold:
160                    st.error(f"高度疑似大语言模型生成文本(生成概率:{final_prob * 100:.1f}%)")
161                elif warning_threshold <= final_prob < high_risk_threshold:
162                    st.warning(f"可疑文本,可能经AI润色或拼接(概率:{final_prob * 100:.1f}%)")
163                else:
164                    st.success(f"真实人类写作风格,低风险(概率:{final_prob * 100:.1f}%)")
165
166                st.progress(float(final_prob))
167                with st.expander("展开底层特征参数", expanded=True):
168                    st.info(
169                        f"**多维度文本特征:**\n\n"
170                        f"- 统计学风格异常度:{result['stat_score'] * 100:.1f}%\n"
171                        f"- DistilBERT 语义鉴别得分:{result['deep_score'] * 100:.1f}%\n"
172                        f"- 补充指标:{result['details']}"
173                    )
174
175        # ----------------- 视频检测 -----------------
176        elif file_type in ['mp4', 'avi', 'mov']:
177            with col_content:
178                st.subheader("视频内容预览")
179                st.video(uploaded_file)
180
181            with col_result:
182                st.subheader("⚙️ 时空特征提取中...")
183                with st.spinner("正在进行关键帧抽帧、光流计算及音频频谱分析..."):
184                    temp_video_path = f"temp_uploaded_video.{file_type}"
185                    with open(temp_video_path, "wb") as f:
186                        uploaded_file.seek(0) # 核心修复:倒带文件指针
187                        f.write(uploaded_file.read())
188
189                    from video_module import analyze_video
190                    result = analyze_video(temp_video_path)
191
192                    if os.path.exists(temp_video_path):
193                        os.remove(temp_video_path)
194
195                if "error" in result:
196                    st.error(result["error"])
197                else:
198                    st.subheader("检测报告")
199                    final_prob = result['avg_probability']
200
201                    if final_prob >= high_risk_threshold:
202                        st.error(f"整体视频高度疑似AI生成(综合概率:{final_prob * 100:.1f}%)")
203                    elif warning_threshold <= final_prob < high_risk_threshold:
204                        st.warning(f"视频存疑,存在异常帧或拼接痕迹(概率:{final_prob * 100:.1f}%)")
205                    else:
206                        st.success(f"未发现明显时序伪造痕迹,低风险(概率:{final_prob * 100:.1f}%)")
207
208                    st.progress(float(final_prob))
209                    with st.expander("展开抽帧分析详情", expanded=True):
210                        st.info(
211                            f"**视频物理特征:**\n\n"
212                            f"- 总帧数:{result['total_frames']} 帧 (帧率 {result['fps']:.1f} fps)\n"
213                            f"- 关键帧采样数:{result['sampled_frames']} 帧\n"
214                            f"- 单帧最高风险值:{result['max_probability'] * 100:.1f}%"
215                        )
216
217# ==========================================
218# 模式二:多模态联合检测
219# ==========================================
220elif "多模态联合检测" in work_mode:
221    st.subheader("🔗 跨模态联合检测场景")
222    st.markdown(
223        "模拟真实审核场景:同时检测**图像/视频**与**配套文本**,通过决策级加权融合输出综合AI生成概率。"
224    )
225
226    # 初始化session_state记忆体(用于自动提取的文本)
227    if 'auto_text' not in st.session_state:
228        st.session_state['auto_text'] = ""
229
230    col_media, col_text = st.columns(2, gap="large")
231
232    with col_media:
233        multi_media = st.file_uploader(
234            "🖼️ / 🎞️ 第一步:上传媒体内容(图片或短视频)",
235            type=["jpg", "png", "jpeg", "mp4", "avi", "mov"],
236            key="multi_media"
237        )
238        file_type = ""
239        if multi_media:
240            file_type = multi_media.name.split('.')[-1].lower()
241            if file_type in ['mp4', 'avi', 'mov']:
242                st.video(multi_media)
243
244                # 智能语音提取按钮
245                if st.button("🎙️ 自动从视频提取语音转文字", use_container_width=True):
246                    with st.spinner("正在加载Whisper模型并转录音频(若视频较长请耐心等待数分钟)..."):
247                        try:
248                            from moviepy import VideoFileClip
249                            import whisper
250
251                            # 1. 保存临时视频
252                            temp_vid = "temp_for_audio.mp4"
253                            with open(temp_vid, "wb") as f:
254                                multi_media.seek(0)
255                                f.write(multi_media.read())
256
257                            # 2. 读取并剥离音频
258                            temp_audio = "temp_audio.wav"
259                            my_clip = VideoFileClip(temp_vid)
260
261                            # 【新增防护】检查视频到底有没有声音!
262                            if my_clip.audio is None:
263                                st.error("❌ 提取失败:检测到该视频没有声音轨道!")
264                            else:
265                                my_clip.audio.write_audiofile(temp_audio, logger=None)
266                                my_clip.close()
267
268                                # 3. 语音识别
269                                model = load_whisper_model()
270                                result = model.transcribe(
271                                    temp_audio,
272                                    language="zh",
273                                    initial_prompt="以下是一段标准的简体中文普通话录音。",
274                                    fp16=False
275                                )
276
277                                st.session_state['auto_text'] = result["text"]
278                                st.success("✅ 提取成功!")
279                                time.sleep(1)  # 停留1秒让用户看到成功提示
280                                st.rerun()
281
282                        except Exception as e:
283                            # 【新增防护】无论出什么错,直接弹在网页上!
284                            st.error(f"❌ 提取失败,底层报错信息:{str(e)}")
285                            st.info(
286                                "💡 提示:如果看到 'ffprobe' 或 'ffmpeg' 等字眼,请确保系统已通过 conda 安装了 ffmpeg。")
287
288                        finally:
289                            # 【新增防护】哪怕中途崩溃,也要把占硬盘的临时文件删掉
290                            if os.path.exists(temp_vid):
291                                try:
292                                    os.remove(temp_vid)
293                                except:
294                                    pass
295                            if os.path.exists(temp_audio):
296                                try:
297                                    os.remove(temp_audio)
298                                except:
299                                    pass
300            else:
301                st.image(multi_media, use_container_width=True)
302
303    with col_text:
304        multi_txt = st.text_area(
305            "📝 第二步:输入配套文本(或点击左侧自动提取)",
306            value=st.session_state['auto_text'],
307            height=200,
308            key="multi_txt"
309        )
310
311    # 联合检测按钮
312    if multi_media and multi_txt:
313        if st.button("🔍 启动跨模态融合鉴别", type="primary", use_container_width=True):
314            st.divider()
315            st.subheader("📊 联合检测报告")
316
317            from text_module import analyze_text
318
319            with st.spinner("并行调用视觉鉴别引擎与文本鉴别引擎..."):
320                file_type = multi_media.name.split('.')[-1].lower()
321                p_media = 0.0
322                media_label = ""
323
324                if file_type in ['mp4', 'avi', 'mov']:
325                    from video_module import analyze_video
326                    temp_path = f"temp_multi_video.{file_type}"
327                    with open(temp_path, "wb") as f:
328                        multi_media.seek(0) # 核心修复:倒带文件指针
329                        f.write(multi_media.read())
330                    res_media = analyze_video(temp_path)
331                    p_media = res_media.get('avg_probability', 0)
332                    media_label = "🎞️ 视频维度AI生成概率"
333                    if os.path.exists(temp_path):
334                        os.remove(temp_path)
335                else:
336                    from image_module import analyze_image
337                    res_media = analyze_image(multi_media)
338                    p_media = res_media['final_probability']
339                    media_label = "🖼️ 图像维度AI生成概率"
340
341                # 文本检测
342                res_txt = analyze_text(multi_txt)
343                p_txt = res_txt['final_probability']
344
345                # 融合权重(视觉60%,文本40%)
346                joint_prob = (p_media * 0.60) + (p_txt * 0.40)
347
348            # 展示三指标
349            col_d1, col_d2, col_d3 = st.columns(3)
350            col_d1.metric(label=media_label, value=f"{p_media * 100:.1f}%")
351            col_d2.metric(label="📝 文本维度AI生成概率", value=f"{p_txt * 100:.1f}%")
352            col_d3.metric(
353                label="🔗 多模态综合判定概率",
354                value=f"{joint_prob * 100:.1f}%",
355                delta="加权融合 (视觉0.6 + 文本0.4)",
356                delta_color="off"
357            )
358
359            st.progress(float(joint_prob))
360
361            # 综合判定
362            if joint_prob >= high_risk_threshold:
363                st.error("🚨 **联合研判结论:高度疑似AI生成的多模态内容**(图像/视频与文本均呈现显著生成特征)")
364            elif warning_threshold <= joint_prob < high_risk_threshold:
365                st.warning("⚠️ **联合研判结论:内容存疑**,可能存在局部AI修改或跨模态不一致,建议人工复核")
366            else:
367                st.success("✅ **联合研判结论:内容安全**,未见明显多模态伪造痕迹")