nchdlhbctm/TraceDetect-AI
0
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("✅ **联合研判结论:内容安全**,未见明显多模态伪造痕迹")