Spaces:
Sleeping
Sleeping
File size: 6,185 Bytes
646535a | 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 | import cv2
import numpy as np
import torch
import torch.nn as nn
from torchvision import models, transforms
from skimage import feature
from scipy.fftpack import dct
from PIL import Image, ImageOps
import streamlit as st
# 引入热力图相关库
from pytorch_grad_cam import GradCAM
from pytorch_grad_cam.utils.model_targets import ClassifierOutputTarget
# ==========================================
# 辅助函数:智能读取图像(修正 EXIF 方向)
# ==========================================
def get_pil_image(image_input):
if isinstance(image_input, str):
img = Image.open(image_input)
else:
image_input.seek(0)
img = Image.open(image_input)
# 强制应用隐藏的 EXIF 旋转信息,还原真实方向
img = ImageOps.exif_transpose(img)
return img.convert('RGB')
def get_cv_image(image_input):
pil_img = get_pil_image(image_input)
# 保证 OpenCV 提取物理特征时,方向与 PIL 绝对一致
return cv2.cvtColor(np.array(pil_img), cv2.COLOR_RGB2BGR)
# ==========================================
# 第一部分:传统物理特征检测 (权重 15%)
# ==========================================
def extract_traditional_features(image_input):
img = get_cv_image(image_input)
if img is None:
return 0.0
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 1. LBP 局部二值模式
lbp = feature.local_binary_pattern(gray, P=8, R=1, method="uniform")
hist, _ = np.histogram(lbp.ravel(), bins=np.arange(0, 11), density=True)
lbp_entropy = -np.sum(hist * np.log2(hist + 1e-7))
# 2. DCT 离散余弦变换
dct_data = dct(dct(gray.T, norm='ortho').T, norm='ortho')
high_freq = np.sum(np.abs(dct_data[10:, 10:]))
total_energy = np.sum(np.abs(dct_data))
dct_ratio = high_freq / (total_energy + 1e-7)
# 3. FFT 快速傅里叶变换
f = np.fft.fft2(gray)
fshift = np.fft.fftshift(f)
magnitude = 20 * np.log(np.abs(fshift) + 1)
h, w = magnitude.shape
center_h, center_w = h // 2, w // 2
top_left = magnitude[0:center_h, 0:center_w]
bottom_right = np.flip(magnitude[center_h + (h % 2):, center_w + (w % 2):])
if top_left.shape != bottom_right.shape:
bottom_right = cv2.resize(bottom_right, (top_left.shape[1], top_left.shape[0]))
fft_sym_error = np.mean(np.abs(top_left - bottom_right))
score = 0.0
if lbp_entropy > 3.6: score += 0.3
if dct_ratio < 0.985: score += 0.1
if fft_sym_error > 13.8: score += 0.1
return min(score, 1.0)
# ==========================================
# 第二部分:深度模型缓存与提取
# ==========================================
@st.cache_resource
def load_deep_image_model():
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = models.mobilenet_v2(weights=None)
num_ftrs = model.classifier[1].in_features
model.classifier[1] = nn.Linear(num_ftrs, 2)
# 加载自己炼丹生成的权重文件
model.load_state_dict(torch.load('mobilenet_finetuned.pth', map_location=device))
model = model.to(device)
model.eval()
return model, device
def extract_deep_features(image_input, model, device):
pil_img = get_pil_image(image_input)
transform = transforms.Compose([
transforms.Resize((224, 224)),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])
input_tensor = transform(pil_img).unsqueeze(0).to(device)
with torch.no_grad():
outputs = model(input_tensor)
probs = torch.softmax(outputs, dim=1)
fake_prob = probs[0][0].item()
return fake_prob
# ==========================================
# 第三部分:多模态加权融合引擎
# ==========================================
def analyze_image(image_input):
trad_score = extract_traditional_features(image_input)
model, device = load_deep_image_model()
deep_score = extract_deep_features(image_input, model, device)
final_prob = (trad_score * 0.15) + (deep_score * 0.85)
return {
'traditional_score': trad_score,
'deep_score': deep_score,
'final_probability': final_prob
}
# ==========================================
# 第四部分:XAI 热力图渲染引擎
# ==========================================
def generate_image_heatmap(image_input, model, device):
"""使用 Grad-CAM 生成热力图,并无损拉伸回原图尺寸,带动态透明度"""
# ⚠️ 获取原图,绝对没有任何 resize 操作干扰!
original_pil_img = get_pil_image(image_input)
orig_width, orig_height = original_pil_img.size
transform = transforms.Compose([
transforms.Resize((224, 224)),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])
input_tensor = transform(original_pil_img).unsqueeze(0).to(device)
target_layers = [model.features[-1]]
try:
with GradCAM(model=model, target_layers=target_layers) as cam:
targets = [ClassifierOutputTarget(0)]
# 跑出 224x224 的 0~1 原始热力图矩阵
grayscale_cam_224 = cam(input_tensor=input_tensor, targets=targets)[0, :]
# 将 224x224 的热力图拉伸回真实的高清原图宽高!
grayscale_cam_resized = cv2.resize(grayscale_cam_224, (orig_width, orig_height))
heatmap_color = cv2.applyColorMap(np.uint8(255 * grayscale_cam_resized), cv2.COLORMAP_JET)
heatmap_color = cv2.cvtColor(heatmap_color, cv2.COLOR_BGR2RGB)
heatmap_color = np.float32(heatmap_color) / 255.0
orig_img_array = np.array(original_pil_img, dtype=np.float32) / 255.0
alpha = grayscale_cam_resized[..., np.newaxis]
alpha = np.power(alpha, 1.5) * 0.65
visualization = orig_img_array * (1 - alpha) + heatmap_color * alpha
return np.uint8(255 * visualization)
except Exception as e:
return np.array(original_pil_img) |