TraceDetect-AI / app.py
nchdlhbctm's picture
Upload 13 files
646535a verified
Raw
History Blame Contribute Delete
17.4 kB
import streamlit as st
import os
import time
from PIL import Image
# 全局缓存 Whisper 语音大模型
@st.cache_resource
def load_whisper_model():
import whisper
return whisper.load_model("small")
# 页面配置
st.set_page_config(
page_title="多模态AI生成痕迹鉴别系统",
page_icon="🔍",
layout="wide"
)
# ==========================================
# 侧边栏:系统设置与说明
# ==========================================
with st.sidebar:
st.title("⚙️ 鉴别引擎设置")
st.markdown("提供轻量、可解释的多模态AI生成内容快速鉴别能力。")
st.divider()
st.header("🎚️ 动态判定阈值")
st.markdown("调整敏感度,适应不同审核场景:")
high_risk_threshold = st.slider(
"高危报警阈值",
min_value=0.60, max_value=0.95, value=0.80, step=0.05,
help="高于此值判定为“高度疑似AI生成”"
)
warning_threshold = st.slider(
"存疑缓冲阈值",
min_value=0.20, max_value=0.55, value=0.40, step=0.05,
help="介于警告阈值与报警阈值之间时,标记为“存疑”"
)
st.divider()
st.caption("🔹 引擎状态:轻量模型已缓存 · 本地推理")
# ==========================================
# 主界面标题
# ==========================================
st.title("🔍 多模态AI生成痕迹快速鉴别系统")
st.markdown(
"同时支持**图像、文本、视频**单模态扫描,以及**图文/视文**联合跨模态融合鉴定。"
)
work_mode = st.radio(
"请选择检测模式:",
["单模态独立检测(图片 / 文本 / 视频)", "多模态联合检测(图片+文案 / 视频+文案)"],
horizontal=True
)
st.divider()
# ==========================================
# 模式一:单模态独立检测
# ==========================================
if "单模态独立检测" in work_mode:
with st.container(border=True):
uploaded_file = st.file_uploader(
"拖拽或点击上传待检测文件",
type=["jpg", "png", "jpeg", "txt", "docx", "mp4", "avi", "mov"],
help="支持常见图片、文本、视频格式,单文件≤50MB,视频时长建议≤5分钟"
)
if uploaded_file is not None:
file_type = uploaded_file.name.split('.')[-1].lower()
st.success(f"✅ 已接收:**{uploaded_file.name}** | 启动鉴别引擎...")
col_content, col_result = st.columns([1, 1.2], gap="large")
# ----------------- 图像检测 -----------------
if file_type in ['jpg', 'png', 'jpeg']:
with col_content:
st.subheader("原始图像")
st.image(uploaded_file, use_container_width=True)
st.subheader("AI痕迹热力图(Grad-CAM)")
heatmap_placeholder = st.empty()
with col_result:
st.subheader("⚙️ 特征提取中...")
progress_bar = st.progress(0)
status_text = st.empty()
for percent_complete in range(100):
time.sleep(0.01)
progress_bar.progress(percent_complete + 1)
status_text.text(f"正在提取LBP纹理、频域伪影及深度语义... {percent_complete + 1}%")
from image_module import analyze_image, load_deep_image_model, generate_image_heatmap
result = analyze_image(uploaded_file)
model, device = load_deep_image_model()
# 图像模块内部我们已经处理过 seek(0),所以这里可以直接传
heatmap_img = generate_image_heatmap(uploaded_file, model, device)
heatmap_placeholder.image(
heatmap_img,
caption="🔴 红色区域为模型判定的高可疑伪造区域",
use_container_width=True
)
status_text.text("特征提取完成")
st.subheader("检测报告")
final_prob = result['final_probability']
if final_prob >= high_risk_threshold:
st.error(f"高度疑似AI生成图像(生成概率:{final_prob * 100:.1f}%)")
elif warning_threshold <= final_prob < high_risk_threshold:
st.warning(f"可疑图像,可能经过AI修图或局部生成(概率:{final_prob * 100:.1f}%)")
else:
st.success(f"真实图像,未检出明显生成痕迹(概率:{final_prob * 100:.1f}%)")
st.progress(float(final_prob))
with st.expander("展开底层特征参数", expanded=True):
st.info(
f"**双轨特征得分:**\n\n"
f"- 传统物理特征异常度:{result['traditional_score'] * 100:.1f}%\n"
f"- MobileNetV2 深度特征:{result['deep_score'] * 100:.1f}%"
)
# ----------------- 文本检测 -----------------
elif file_type in ['txt', 'docx']:
text_content = ""
uploaded_file.seek(0) # 核心修复:倒带文件指针
if file_type == 'txt':
text_content = uploaded_file.read().decode("utf-8")
elif file_type == 'docx':
import docx
doc = docx.Document(uploaded_file)
text_content = "\n".join([para.text for para in doc.paragraphs])
with col_content:
st.subheader("原始文本内容")
text_placeholder = st.empty()
text_placeholder.text_area("提取的文本", text_content, height=350)
with col_result:
st.subheader("⚙️ 语义连贯性分析中...")
with st.spinner("正在计算困惑度、句法复杂度及BERT深度特征..."):
from text_module import analyze_text, get_custom_text_model, generate_text_highlight_html
result = analyze_text(text_content)
tokenizer, model = get_custom_text_model()
highlighted_html = generate_text_highlight_html(text_content, tokenizer, model)
text_placeholder.markdown("**🔥 AI痕迹逐句高亮(黄色为高风险句式)**", unsafe_allow_html=True)
text_placeholder.markdown(highlighted_html, unsafe_allow_html=True)
st.subheader("检测报告")
final_prob = result['final_probability']
if final_prob >= high_risk_threshold:
st.error(f"高度疑似大语言模型生成文本(生成概率:{final_prob * 100:.1f}%)")
elif warning_threshold <= final_prob < high_risk_threshold:
st.warning(f"可疑文本,可能经AI润色或拼接(概率:{final_prob * 100:.1f}%)")
else:
st.success(f"真实人类写作风格,低风险(概率:{final_prob * 100:.1f}%)")
st.progress(float(final_prob))
with st.expander("展开底层特征参数", expanded=True):
st.info(
f"**多维度文本特征:**\n\n"
f"- 统计学风格异常度:{result['stat_score'] * 100:.1f}%\n"
f"- DistilBERT 语义鉴别得分:{result['deep_score'] * 100:.1f}%\n"
f"- 补充指标:{result['details']}"
)
# ----------------- 视频检测 -----------------
elif file_type in ['mp4', 'avi', 'mov']:
with col_content:
st.subheader("视频内容预览")
st.video(uploaded_file)
with col_result:
st.subheader("⚙️ 时空特征提取中...")
with st.spinner("正在进行关键帧抽帧、光流计算及音频频谱分析..."):
temp_video_path = f"temp_uploaded_video.{file_type}"
with open(temp_video_path, "wb") as f:
uploaded_file.seek(0) # 核心修复:倒带文件指针
f.write(uploaded_file.read())
from video_module import analyze_video
result = analyze_video(temp_video_path)
if os.path.exists(temp_video_path):
os.remove(temp_video_path)
if "error" in result:
st.error(result["error"])
else:
st.subheader("检测报告")
final_prob = result['avg_probability']
if final_prob >= high_risk_threshold:
st.error(f"整体视频高度疑似AI生成(综合概率:{final_prob * 100:.1f}%)")
elif warning_threshold <= final_prob < high_risk_threshold:
st.warning(f"视频存疑,存在异常帧或拼接痕迹(概率:{final_prob * 100:.1f}%)")
else:
st.success(f"未发现明显时序伪造痕迹,低风险(概率:{final_prob * 100:.1f}%)")
st.progress(float(final_prob))
with st.expander("展开抽帧分析详情", expanded=True):
st.info(
f"**视频物理特征:**\n\n"
f"- 总帧数:{result['total_frames']} 帧 (帧率 {result['fps']:.1f} fps)\n"
f"- 关键帧采样数:{result['sampled_frames']} 帧\n"
f"- 单帧最高风险值:{result['max_probability'] * 100:.1f}%"
)
# ==========================================
# 模式二:多模态联合检测
# ==========================================
elif "多模态联合检测" in work_mode:
st.subheader("🔗 跨模态联合检测场景")
st.markdown(
"模拟真实审核场景:同时检测**图像/视频**与**配套文本**,通过决策级加权融合输出综合AI生成概率。"
)
# 初始化session_state记忆体(用于自动提取的文本)
if 'auto_text' not in st.session_state:
st.session_state['auto_text'] = ""
col_media, col_text = st.columns(2, gap="large")
with col_media:
multi_media = st.file_uploader(
"🖼️ / 🎞️ 第一步:上传媒体内容(图片或短视频)",
type=["jpg", "png", "jpeg", "mp4", "avi", "mov"],
key="multi_media"
)
file_type = ""
if multi_media:
file_type = multi_media.name.split('.')[-1].lower()
if file_type in ['mp4', 'avi', 'mov']:
st.video(multi_media)
# 智能语音提取按钮
if st.button("🎙️ 自动从视频提取语音转文字", use_container_width=True):
with st.spinner("正在加载Whisper模型并转录音频(若视频较长请耐心等待数分钟)..."):
try:
from moviepy import VideoFileClip
import whisper
# 1. 保存临时视频
temp_vid = "temp_for_audio.mp4"
with open(temp_vid, "wb") as f:
multi_media.seek(0)
f.write(multi_media.read())
# 2. 读取并剥离音频
temp_audio = "temp_audio.wav"
my_clip = VideoFileClip(temp_vid)
# 【新增防护】检查视频到底有没有声音!
if my_clip.audio is None:
st.error("❌ 提取失败:检测到该视频没有声音轨道!")
else:
my_clip.audio.write_audiofile(temp_audio, logger=None)
my_clip.close()
# 3. 语音识别
model = load_whisper_model()
result = model.transcribe(
temp_audio,
language="zh",
initial_prompt="以下是一段标准的简体中文普通话录音。",
fp16=False
)
st.session_state['auto_text'] = result["text"]
st.success("✅ 提取成功!")
time.sleep(1) # 停留1秒让用户看到成功提示
st.rerun()
except Exception as e:
# 【新增防护】无论出什么错,直接弹在网页上!
st.error(f"❌ 提取失败,底层报错信息:{str(e)}")
st.info(
"💡 提示:如果看到 'ffprobe' 或 'ffmpeg' 等字眼,请确保系统已通过 conda 安装了 ffmpeg。")
finally:
# 【新增防护】哪怕中途崩溃,也要把占硬盘的临时文件删掉
if os.path.exists(temp_vid):
try:
os.remove(temp_vid)
except:
pass
if os.path.exists(temp_audio):
try:
os.remove(temp_audio)
except:
pass
else:
st.image(multi_media, use_container_width=True)
with col_text:
multi_txt = st.text_area(
"📝 第二步:输入配套文本(或点击左侧自动提取)",
value=st.session_state['auto_text'],
height=200,
key="multi_txt"
)
# 联合检测按钮
if multi_media and multi_txt:
if st.button("🔍 启动跨模态融合鉴别", type="primary", use_container_width=True):
st.divider()
st.subheader("📊 联合检测报告")
from text_module import analyze_text
with st.spinner("并行调用视觉鉴别引擎与文本鉴别引擎..."):
file_type = multi_media.name.split('.')[-1].lower()
p_media = 0.0
media_label = ""
if file_type in ['mp4', 'avi', 'mov']:
from video_module import analyze_video
temp_path = f"temp_multi_video.{file_type}"
with open(temp_path, "wb") as f:
multi_media.seek(0) # 核心修复:倒带文件指针
f.write(multi_media.read())
res_media = analyze_video(temp_path)
p_media = res_media.get('avg_probability', 0)
media_label = "🎞️ 视频维度AI生成概率"
if os.path.exists(temp_path):
os.remove(temp_path)
else:
from image_module import analyze_image
res_media = analyze_image(multi_media)
p_media = res_media['final_probability']
media_label = "🖼️ 图像维度AI生成概率"
# 文本检测
res_txt = analyze_text(multi_txt)
p_txt = res_txt['final_probability']
# 融合权重(视觉60%,文本40%)
joint_prob = (p_media * 0.60) + (p_txt * 0.40)
# 展示三指标
col_d1, col_d2, col_d3 = st.columns(3)
col_d1.metric(label=media_label, value=f"{p_media * 100:.1f}%")
col_d2.metric(label="📝 文本维度AI生成概率", value=f"{p_txt * 100:.1f}%")
col_d3.metric(
label="🔗 多模态综合判定概率",
value=f"{joint_prob * 100:.1f}%",
delta="加权融合 (视觉0.6 + 文本0.4)",
delta_color="off"
)
st.progress(float(joint_prob))
# 综合判定
if joint_prob >= high_risk_threshold:
st.error("🚨 **联合研判结论:高度疑似AI生成的多模态内容**(图像/视频与文本均呈现显著生成特征)")
elif warning_threshold <= joint_prob < high_risk_threshold:
st.warning("⚠️ **联合研判结论:内容存疑**,可能存在局部AI修改或跨模态不一致,建议人工复核")
else:
st.success("✅ **联合研判结论:内容安全**,未见明显多模态伪造痕迹")