aaappp7878 commited on
Commit
1f1e15a
·
verified ·
1 Parent(s): 142ac3f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +589 -125
app.py CHANGED
@@ -5,10 +5,14 @@ import cv2
5
  from PIL import Image
6
  import time
7
  import os
 
 
8
  from scipy import stats
9
  from skimage.feature import graycomatrix, graycoprops, local_binary_pattern
10
  from transformers import AutoImageProcessor, AutoModelForImageClassification
11
  from functools import lru_cache
 
 
12
 
13
  # 设置缓存目录
14
  os.environ["TRANSFORMERS_CACHE"] = "/tmp/transformers_cache"
@@ -16,32 +20,36 @@ os.environ["HF_HOME"] = "/tmp/hf_home"
16
  os.makedirs("/tmp/transformers_cache", exist_ok=True)
17
  os.makedirs("/tmp/hf_home", exist_ok=True)
18
 
 
 
 
 
19
  #############################################
20
- # 模型管理部分
21
  #############################################
22
 
23
- class ModelManager:
24
- """Manages model loading, caching and inference"""
25
 
26
  def __init__(self):
27
  self.models = {
28
- "model1": {
29
- "name": "umm-maybe/AI-image-detector",
30
  "processor": None,
31
  "model": None,
32
- "weight": 0.5
33
  },
34
- "model2": {
35
- "name": "microsoft/resnet-50",
36
  "processor": None,
37
  "model": None,
38
- "weight": 0.25
39
  },
40
- "model3": {
41
- "name": "google/vit-base-patch16-224",
42
  "processor": None,
43
  "model": None,
44
- "weight": 0.25
45
  }
46
  }
47
  self.loaded_models = set()
@@ -116,7 +124,8 @@ class ModelManager:
116
  return {
117
  "model_name": model_info["name"],
118
  "ai_probability": ai_probability,
119
- "predicted_class": model_info["model"].config.id2label[predicted_class_idx]
 
120
  }
121
 
122
  except Exception as e:
@@ -155,7 +164,7 @@ class ModelManager:
155
  predicted_class = model_info["model"].config.id2label[predicted_class_idx].lower()
156
 
157
  # 检查AI相关关键词
158
- ai_keywords = ["artificial", "generated", "synthetic", "fake", "computer"]
159
  for keyword in ai_keywords:
160
  if keyword in predicted_class:
161
  return float(probabilities[0][predicted_class_idx].item())
@@ -164,14 +173,13 @@ class ModelManager:
164
  if "ai" in predicted_class or "generated" in predicted_class or "fake" in predicted_class:
165
  return float(probabilities[0][predicted_class_idx].item())
166
  else:
167
- return 1 - float(probabilities[0][predicted_class_idx].item())
168
-
169
  #############################################
170
- # 特征提取部分
171
  #############################################
172
 
173
- class FeatureExtractor:
174
- """Optimized image feature extraction"""
175
 
176
  def __init__(self):
177
  # 中间结果缓存
@@ -181,6 +189,31 @@ class FeatureExtractor:
181
  """Clear the cache between images"""
182
  self.cache = {}
183
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
184
  @lru_cache(maxsize=8)
185
  def get_grayscale(self, image_id):
186
  """Get grayscale version of image with caching"""
@@ -217,7 +250,12 @@ class FeatureExtractor:
217
  image_id = id(image)
218
  self.cache['image_id'] = image_id
219
 
 
 
 
 
220
  features = {}
 
221
 
222
  # 基本特征
223
  features["width"] = image.width
@@ -231,6 +269,7 @@ class FeatureExtractor:
231
  self._extract_noise_features(img_cv, features)
232
  self._extract_symmetry_features(img_cv, features)
233
  self._extract_frequency_features(img_cv, features, image_id)
 
234
 
235
  return features
236
 
@@ -276,6 +315,18 @@ class FeatureExtractor:
276
  g_entropy = stats.entropy(g_hist + 1e-10)
277
  b_entropy = stats.entropy(b_hist + 1e-10)
278
  features["color_entropy"] = float((r_entropy + g_entropy + b_entropy) / 3)
 
 
 
 
 
 
 
 
 
 
 
 
279
 
280
  def _extract_edge_features(self, img_cv, features, image_id):
281
  """Extract edge-related features"""
@@ -312,6 +363,10 @@ class FeatureExtractor:
312
  if np.sum(mask) > 0:
313
  edge_dir_hist, _ = np.histogram(edge_direction[mask], bins=18, range=(-180, 180))
314
  features["edge_direction_entropy"] = float(stats.entropy(edge_dir_hist + 1e-10))
 
 
 
 
315
 
316
  def _extract_texture_features(self, img_cv, features, image_id):
317
  """Extract texture-related features"""
@@ -375,6 +430,9 @@ class FeatureExtractor:
375
  lbp_hist, _ = np.histogram(lbp, bins=n_points + 2, range=(0, n_points + 2))
376
  lbp_hist = lbp_hist.astype(float) / (sum(lbp_hist) + 1e-10)
377
  features["lbp_entropy"] = float(stats.entropy(lbp_hist + 1e-10))
 
 
 
378
  except Exception as e:
379
  print(f"LBP计算错误: {e}")
380
 
@@ -406,6 +464,18 @@ class FeatureExtractor:
406
 
407
  if noise_blocks:
408
  features["noise_spatial_std"] = float(np.std(noise_blocks))
 
 
 
 
 
 
 
 
 
 
 
 
409
 
410
  def _extract_symmetry_features(self, img_cv, features):
411
  """Extract symmetry-related features"""
@@ -438,6 +508,33 @@ class FeatureExtractor:
438
  diff = cv2.absdiff(top_half, bottom_half)
439
  v_symmetry = 1 - float(np.mean(diff) / 255)
440
  features["vertical_symmetry"] = v_symmetry
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
441
 
442
  def _extract_frequency_features(self, img_cv, features, image_id):
443
  """Extract frequency domain features"""
@@ -485,60 +582,249 @@ class FeatureExtractor:
485
  cv2.ellipse(mask, (center_w, center_h), (w//2, h//2), angle, -10, 10, 1, -1)
486
  freq_blocks.append(np.mean(magnitude * mask))
487
  features["freq_anisotropy"] = float(np.std(freq_blocks))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
488
  #############################################
489
- # 分析逻辑部分
490
  #############################################
491
 
492
- # 初始化管理器
493
- model_manager = ModelManager()
494
- feature_extractor = FeatureExtractor()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
495
 
496
- # 跟踪对AI检测最重要的特征
497
- CRITICAL_FEATURES = {
498
- "lbp_entropy": {"threshold": 2.5, "weight": 0.4},
499
- "freq_anisotropy": {"threshold": 0.1, "weight": 0.4},
500
- "detail_spatial_std": {"threshold": 5, "weight": 0.3},
501
- "texture_correlation": {"threshold": 0.9, "weight": 0.15},
502
- "horizontal_symmetry": {"threshold": 0.7, "weight": 0.1},
503
- "vertical_symmetry": {"threshold": 0.7, "weight": 0.1},
504
- "noise_spatial_std": {"threshold": 0.5, "weight": 0.15},
505
- "freq_ratio": {"threshold": 0.1, "weight": 0.1},
506
- "noise_spectrum_std": {"threshold": 1000, "weight": 0.15},
507
- "color_entropy": {"threshold": 5, "weight": 0.15}
 
 
 
 
508
  }
509
 
 
 
 
 
 
 
 
 
510
  def check_ai_specific_features(image_features):
511
- """Optimized check for AI-generated image features"""
512
  ai_score = 0
513
  ai_signs = []
514
 
515
- # 只处理最关键的特征
516
- for feature_name, config in CRITICAL_FEATURES.items():
 
 
 
517
  if feature_name not in image_features:
518
  continue
519
 
520
  value = image_features[feature_name]
521
- threshold = config["threshold"]
522
- weight = config["weight"]
 
 
523
 
524
  # 不同特征有不同的比较逻辑
525
- if feature_name in ["lbp_entropy", "freq_anisotropy", "detail_spatial_std",
526
- "noise_spatial_std", "freq_ratio", "noise_spectrum_std",
527
- "color_entropy"]:
 
 
 
528
  if value < threshold:
529
- ai_score += weight
530
- ai_signs.append(f"{feature_name} 异常低 ({value:.2f})")
531
- elif feature_name in ["texture_correlation", "horizontal_symmetry", "vertical_symmetry"]:
 
 
 
 
532
  if value > threshold:
533
- ai_score += weight
534
- ai_signs.append(f"{feature_name} 异常高 ({value:.2f})")
 
 
 
 
535
 
536
  # 计算检测到多少关键特征
537
  critical_count = len(ai_signs)
538
  if critical_count >= 5:
539
- ai_score = max(ai_score, 0.9)
540
  elif critical_count >= 3:
541
- ai_score = max(ai_score, 0.7)
542
 
543
  return min(ai_score, 1.0), ai_signs
544
 
@@ -547,22 +833,34 @@ def detect_beauty_filter_signs(image_features):
547
  beauty_score = 0
548
  beauty_signs = []
549
 
 
 
 
550
  # 只检查最重要的美颜滤镜指标
551
  if "face_skin_std" in image_features:
552
- if image_features["face_skin_std"] < 15:
 
553
  beauty_score += 0.3
554
  beauty_signs.append("皮肤质感过于均匀")
555
 
556
  if "edge_density" in image_features:
557
- if image_features["edge_density"] < 0.03:
 
558
  beauty_score += 0.2
559
  beauty_signs.append("边缘过于平滑")
560
 
561
  if "noise_level" in image_features:
562
- if image_features["noise_level"] < 1.0:
 
563
  beauty_score += 0.2
564
  beauty_signs.append("噪点异常少")
565
 
 
 
 
 
 
 
566
  return min(beauty_score, 1.0), beauty_signs
567
 
568
  def detect_photoshop_signs(image_features):
@@ -570,67 +868,114 @@ def detect_photoshop_signs(image_features):
570
  ps_score = 0
571
  ps_signs = []
572
 
 
 
 
573
  # 只检查最重要的PS指标
574
  if "texture_homogeneity" in image_features:
575
- if image_features["texture_homogeneity"] > 0.4:
 
576
  ps_score += 0.2
577
  ps_signs.append("皮肤质感过于均匀")
578
 
579
  if "edge_density" in image_features:
580
- if image_features["edge_density"] < 0.01:
 
581
  ps_score += 0.2
582
  ps_signs.append("边缘过于平滑")
583
 
584
  if "color_std" in image_features:
585
- if image_features["color_std"] > 50:
 
586
  ps_score += 0.2
587
  ps_signs.append("颜色分布极不自然")
588
 
 
 
 
 
 
 
589
  return min(ps_score, 1.0), ps_signs
590
 
591
- def get_detailed_analysis(ai_probability, ps_score, beauty_score, ps_signs, ai_signs, beauty_signs, valid_models_count, ai_feature_score):
592
- """Provide detailed analysis with two-level classification"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
593
 
594
- # 根据模型数量调整置信度
595
- confidence_prefix = ""
596
- if valid_models_count >= 3:
 
 
 
 
 
597
  confidence_prefix = "极高置信度:"
598
- elif valid_models_count == 2:
599
  confidence_prefix = "高置信度:"
600
- elif valid_models_count == 1:
601
  confidence_prefix = "中等置信度:"
 
 
602
 
603
  # 计算编辑分数(在所有路径中都需要)
604
  combined_edit_score = max(ps_score, beauty_score)
605
 
 
 
 
 
 
 
 
606
  # 处理特征与模型判断不一致的情况
607
- if ai_feature_score > 0.8 and ai_probability < 0.6:
608
- ai_probability = max(0.8, ai_probability)
609
- category = confidence_prefix + "AI生成图像(基于特征分析)"
610
- description = "基于多种典型AI特征分析,该图像很可能是AI生成的,尽管模型判断结果不确定。"
611
  main_category = "AI生成"
612
- elif ai_feature_score > 0.6 and ai_probability < 0.5:
613
- ai_probability = max(0.7, ai_probability)
614
 
615
  # 第一级分类:AI vs 真实
616
  if ai_probability > 0.6:
617
- category = confidence_prefix + "AI生成图像"
618
  description = "图像很可能是由AI完全生成,几乎没有真人照片的特征。"
619
  main_category = "AI生成"
620
  else:
621
  # 第二级分类:素人 vs 修图
622
  if combined_edit_score > 0.5:
623
- category = confidence_prefix + "真人照片,修图痕迹明显"
624
  description = "图像基本是真人照片,但经过了明显的后期处理或美颜,修饰痕迹明显。"
625
  main_category = "真人照片-修图明显"
626
  else:
627
- category = confidence_prefix + "真实素人照片"
628
  description = "图像很可能是未经大量处理的真人照片,保留了自然的细节和特征。"
629
  main_category = "真人照片-素人"
630
 
631
- # 处理边界情况
632
- if ai_probability > 0.45 and combined_edit_score > 0.7:
633
- category = confidence_prefix + "真人照片,修图痕迹明显(也可能是AI生成)"
 
 
 
 
 
 
634
  description = "图像可能是真人照片经过大量后期处理,也可能是AI生成图像。由于现代AI技术与高度修图效果相似,难以完全区分。"
635
  main_category = "真人照片-修图明显"
636
 
@@ -641,13 +986,31 @@ def get_detailed_analysis(ai_probability, ps_score, beauty_score, ps_signs, ai_s
641
 
642
  return category, description, ps_details, ai_details, beauty_details, main_category
643
  def detect_ai_image(image):
644
- """Main detection function with optimizations"""
645
  if image is None:
646
  return {"error": "未提供图像"}
647
 
648
  start_time = time.time()
 
 
 
 
 
649
 
650
- # 步骤1:获取模型预测(仅在需要时加载模型)
 
 
 
 
 
 
 
 
 
 
 
 
 
651
  results = {}
652
  valid_models = 0
653
  weighted_ai_probability = 0
@@ -672,72 +1035,79 @@ def detect_ai_image(image):
672
  else:
673
  return {"error": "所有模型加载失败"}
674
 
675
- # 步骤2:提取图像特征(对大图像进行降采样)
676
- # 确定是否需要降采样
677
- downscale_factor = 1.0
678
- if image.width * image.height > 1024 * 1024: # 对于大于1MP的图像
679
- downscale_factor = min(1.0, 1024 * 1024 / (image.width * image.height))
680
-
681
- # 提取特征
682
- feature_extractor.clear_cache() # 清除之前运行的缓存
683
- image_features = feature_extractor.analyze_image_features(image, downscale_factor)
684
 
685
- # 步骤3:分析特征
686
- ai_feature_score, ai_signs = check_ai_specific_features(image_features)
687
  ps_score, ps_signs = detect_photoshop_signs(image_features)
688
  beauty_score, beauty_signs = detect_beauty_filter_signs(image_features)
689
 
690
- # 步骤4:根据特征调整概率
691
  adjusted_probability = final_ai_probability
692
 
693
- # 增加特征分析的权重
694
- if ai_feature_score > 0.8:
695
- adjusted_probability = max(adjusted_probability, 0.8)
696
- elif ai_feature_score > 0.6:
697
- adjusted_probability = max(adjusted_probability, 0.7)
698
- elif ai_feature_score > 0.4:
699
- adjusted_probability = max(adjusted_probability, 0.6)
 
 
 
 
 
 
 
 
 
 
700
 
701
  # 检查关键特征
702
  key_ai_features_count = 0
703
 
704
  # LBP熵(微观纹理分析)
705
- if "lbp_entropy" in image_features and image_features["lbp_entropy"] < 2.5:
706
- key_ai_features_count += 1
707
- adjusted_probability += 0.1
 
708
 
709
  # 频率各向异性
710
- if "freq_anisotropy" in image_features and image_features["freq_anisotropy"] < 0.1:
711
- key_ai_features_count += 1
712
- adjusted_probability += 0.1
 
713
 
714
- # 细节空间分布
715
- if "detail_spatial_std" in image_features and image_features["detail_spatial_std"] < 5:
716
- key_ai_features_count += 1
717
- adjusted_probability += 0.1
 
718
 
719
- # 多个关键特征强烈表明AI生成
720
- if key_ai_features_count >= 2:
721
- adjusted_probability = max(adjusted_probability, 0.7)
722
 
723
  # 确保概率在有效范围内
724
  adjusted_probability = min(1.0, max(0.0, adjusted_probability))
725
 
726
- # 步骤5:获取详细分析
727
  category, description, ps_details, ai_details, beauty_details, main_category = get_detailed_analysis(
728
  adjusted_probability, ps_score, beauty_score, ps_signs, ai_signs, beauty_signs,
729
- valid_models, ai_feature_score
730
  )
731
 
732
  # 构建最终结果
733
  processing_time = time.time() - start_time
734
 
735
  final_result = {
 
736
  "ai_probability": adjusted_probability,
737
  "original_ai_probability": final_ai_probability,
738
  "ps_score": ps_score,
739
  "beauty_score": beauty_score,
740
  "ai_feature_score": ai_feature_score,
 
741
  "category": category,
742
  "main_category": main_category,
743
  "description": description,
@@ -747,26 +1117,120 @@ def detect_ai_image(image):
747
  "processing_time": f"{processing_time:.2f} seconds",
748
  "individual_model_results": results,
749
  # 只包含最重要的特征以减少响应大小
750
- "key_features": {k: image_features[k] for k in CRITICAL_FEATURES if k in image_features}
751
  }
752
 
 
 
 
 
 
 
 
753
  # 返回两个值:JSON结果和标签数据
754
  label_data = {main_category: 1.0}
755
  return final_result, label_data
756
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
757
  # 创建Gradio界面
758
- iface = gr.Interface(
759
- fn=detect_ai_image,
760
- inputs=gr.Image(type="pil"),
761
- outputs=[
762
- gr.JSON(label="详细分析结果"),
763
- gr.Label(label="主要分类", num_top_classes=1)
764
- ],
765
- title="优化版AI图像检测API",
766
- description="多模型集成检测图像是否由AI生成或真人照片(素人/修图)",
767
- examples=None,
768
- allow_flagging="never"
769
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
770
 
771
  # 启动应用
772
- iface.launch(share=True)
 
 
5
  from PIL import Image
6
  import time
7
  import os
8
+ import json
9
+ from datetime import datetime
10
  from scipy import stats
11
  from skimage.feature import graycomatrix, graycoprops, local_binary_pattern
12
  from transformers import AutoImageProcessor, AutoModelForImageClassification
13
  from functools import lru_cache
14
+ import pywt # 用于小波变换
15
+ import uuid # 用于生成唯一ID
16
 
17
  # 设置缓存目录
18
  os.environ["TRANSFORMERS_CACHE"] = "/tmp/transformers_cache"
 
20
  os.makedirs("/tmp/transformers_cache", exist_ok=True)
21
  os.makedirs("/tmp/hf_home", exist_ok=True)
22
 
23
+ # 创建反馈存储目录
24
+ FEEDBACK_DIR = "/tmp/feedback"
25
+ os.makedirs(FEEDBACK_DIR, exist_ok=True)
26
+
27
  #############################################
28
+ # 模型管理部分 - 增强版
29
  #############################################
30
 
31
+ class EnhancedModelManager:
32
+ """Enhanced model manager with support for specialized AI detection models"""
33
 
34
  def __init__(self):
35
  self.models = {
36
+ "ai_detector": {
37
+ "name": "umm-maybe/AI-image-detector", # 将来替换为CNNDetection
38
  "processor": None,
39
  "model": None,
40
+ "weight": 0.6 # 增加专业AI检测模型权重
41
  },
42
+ "general_classifier1": {
43
+ "name": "microsoft/resnet-50", # 将来替换为DFDC模型
44
  "processor": None,
45
  "model": None,
46
+ "weight": 0.2 # 降低通用模型权重
47
  },
48
+ "general_classifier2": {
49
+ "name": "google/vit-base-patch16-224", # 保留作为辅助模型
50
  "processor": None,
51
  "model": None,
52
+ "weight": 0.2 # 降低通用模型权重
53
  }
54
  }
55
  self.loaded_models = set()
 
124
  return {
125
  "model_name": model_info["name"],
126
  "ai_probability": ai_probability,
127
+ "predicted_class": model_info["model"].config.id2label[predicted_class_idx],
128
+ "confidence": float(probabilities[0][predicted_class_idx].item())
129
  }
130
 
131
  except Exception as e:
 
164
  predicted_class = model_info["model"].config.id2label[predicted_class_idx].lower()
165
 
166
  # 检查AI相关关键词
167
+ ai_keywords = ["artificial", "generated", "synthetic", "fake", "computer", "digital", "cgi", "rendered"]
168
  for keyword in ai_keywords:
169
  if keyword in predicted_class:
170
  return float(probabilities[0][predicted_class_idx].item())
 
173
  if "ai" in predicted_class or "generated" in predicted_class or "fake" in predicted_class:
174
  return float(probabilities[0][predicted_class_idx].item())
175
  else:
176
+ return 0.3 # 降低默认AI概率,更保守的判断
 
177
  #############################################
178
+ # 特征提取部分 - 增强版
179
  #############################################
180
 
181
+ class EnhancedFeatureExtractor:
182
+ """Enhanced image feature extraction with advanced features"""
183
 
184
  def __init__(self):
185
  # 中间结果缓存
 
189
  """Clear the cache between images"""
190
  self.cache = {}
191
 
192
+ def detect_image_type(self, image):
193
+ """Detect the type of image (portrait, landscape, etc.)"""
194
+ img_array = np.array(image)
195
+
196
+ # 检测人脸
197
+ try:
198
+ face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')
199
+ if len(img_array.shape) == 3:
200
+ gray = cv2.cvtColor(img_array, cv2.COLOR_RGB2GRAY)
201
+ else:
202
+ gray = img_array
203
+ faces = face_cascade.detectMultiScale(gray, 1.1, 4)
204
+
205
+ if len(faces) > 0:
206
+ return "portrait"
207
+ except:
208
+ pass
209
+
210
+ # 检查是否为风景图 (简单启发式方法)
211
+ if image.width > image.height * 1.5:
212
+ return "landscape"
213
+
214
+ # 默认类型
215
+ return "general"
216
+
217
  @lru_cache(maxsize=8)
218
  def get_grayscale(self, image_id):
219
  """Get grayscale version of image with caching"""
 
250
  image_id = id(image)
251
  self.cache['image_id'] = image_id
252
 
253
+ # 检测图像类型
254
+ image_type = self.detect_image_type(image)
255
+ self.cache['image_type'] = image_type
256
+
257
  features = {}
258
+ features["image_type"] = image_type
259
 
260
  # 基本特征
261
  features["width"] = image.width
 
269
  self._extract_noise_features(img_cv, features)
270
  self._extract_symmetry_features(img_cv, features)
271
  self._extract_frequency_features(img_cv, features, image_id)
272
+ self._extract_advanced_features(img_cv, features, image_id)
273
 
274
  return features
275
 
 
315
  g_entropy = stats.entropy(g_hist + 1e-10)
316
  b_entropy = stats.entropy(b_hist + 1e-10)
317
  features["color_entropy"] = float((r_entropy + g_entropy + b_entropy) / 3)
318
+
319
+ # 颜色一致性 - AI生成图像通常颜色过于一致
320
+ color_blocks = []
321
+ for i in range(0, h-block_size, stride):
322
+ for j in range(0, w-block_size, stride):
323
+ block = img_array[i:i+block_size, j:j+block_size]
324
+ avg_color = np.mean(block, axis=(0,1))
325
+ color_blocks.append(avg_color)
326
+
327
+ if color_blocks:
328
+ color_blocks = np.array(color_blocks)
329
+ features["color_consistency"] = float(1.0 - np.std(color_blocks) / 128.0)
330
 
331
  def _extract_edge_features(self, img_cv, features, image_id):
332
  """Extract edge-related features"""
 
363
  if np.sum(mask) > 0:
364
  edge_dir_hist, _ = np.histogram(edge_direction[mask], bins=18, range=(-180, 180))
365
  features["edge_direction_entropy"] = float(stats.entropy(edge_dir_hist + 1e-10))
366
+
367
+ # 边缘方向一致性 - AI生成图像通常边缘方向过于一致
368
+ edge_dir_normalized = edge_dir_hist / np.sum(edge_dir_hist)
369
+ features["edge_direction_consistency"] = float(np.max(edge_dir_normalized))
370
 
371
  def _extract_texture_features(self, img_cv, features, image_id):
372
  """Extract texture-related features"""
 
430
  lbp_hist, _ = np.histogram(lbp, bins=n_points + 2, range=(0, n_points + 2))
431
  lbp_hist = lbp_hist.astype(float) / (sum(lbp_hist) + 1e-10)
432
  features["lbp_entropy"] = float(stats.entropy(lbp_hist + 1e-10))
433
+
434
+ # LBP一致性 - AI生成图像通常LBP模式过于一致
435
+ features["lbp_uniformity"] = float(np.sum(lbp_hist**2))
436
  except Exception as e:
437
  print(f"LBP计算错误: {e}")
438
 
 
464
 
465
  if noise_blocks:
466
  features["noise_spatial_std"] = float(np.std(noise_blocks))
467
+
468
+ # 噪声颜色通道相关性 - 真实照片的噪声在通道间相关性较低
469
+ if len(img_cv.shape) == 3:
470
+ noise_r = noise[:,:,0].flatten()
471
+ noise_g = noise[:,:,1].flatten()
472
+ noise_b = noise[:,:,2].flatten()
473
+
474
+ corr_rg = np.corrcoef(noise_r, noise_g)[0,1]
475
+ corr_rb = np.corrcoef(noise_r, noise_b)[0,1]
476
+ corr_gb = np.corrcoef(noise_g, noise_b)[0,1]
477
+
478
+ features["noise_channel_correlation"] = float((abs(corr_rg) + abs(corr_rb) + abs(corr_gb)) / 3)
479
 
480
  def _extract_symmetry_features(self, img_cv, features):
481
  """Extract symmetry-related features"""
 
508
  diff = cv2.absdiff(top_half, bottom_half)
509
  v_symmetry = 1 - float(np.mean(diff) / 255)
510
  features["vertical_symmetry"] = v_symmetry
511
+
512
+ # 径向对称性 - 对于人脸等中心对象很有用
513
+ if min(h, w) > 100: # 只对足够大的图像计算
514
+ try:
515
+ center_y, center_x = h // 2, w // 2
516
+ max_radius = min(center_x, center_y) - 10
517
+
518
+ if max_radius > 20: # 确保有足够的半径
519
+ # 创建径向对称性掩码
520
+ y, x = np.ogrid[-center_y:h-center_y, -center_x:w-center_x]
521
+ mask = x*x + y*y <= max_radius*max_radius
522
+
523
+ # 计算对称性
524
+ masked_img = img_cv.copy()
525
+ if len(masked_img.shape) == 3:
526
+ for c in range(masked_img.shape[2]):
527
+ masked_img[:,:,c][~mask] = 0
528
+ else:
529
+ masked_img[~mask] = 0
530
+
531
+ # 旋转180度比较
532
+ rotated = cv2.rotate(masked_img, cv2.ROTATE_180)
533
+ diff = cv2.absdiff(masked_img, rotated)
534
+
535
+ features["radial_symmetry"] = 1 - float(np.mean(diff) / 255)
536
+ except:
537
+ pass
538
 
539
  def _extract_frequency_features(self, img_cv, features, image_id):
540
  """Extract frequency domain features"""
 
582
  cv2.ellipse(mask, (center_w, center_h), (w//2, h//2), angle, -10, 10, 1, -1)
583
  freq_blocks.append(np.mean(magnitude * mask))
584
  features["freq_anisotropy"] = float(np.std(freq_blocks))
585
+
586
+ # 频率峰值分析 - AI生成图像通常有特定的频率峰值模式
587
+ try:
588
+ # 计算径向平均功率谱
589
+ y, x = np.ogrid[-center_h:h-center_h, -center_w:w-center_w]
590
+ r = np.sqrt(x*x + y*y)
591
+ r = r.astype(np.int32)
592
+
593
+ # 创建径向平均
594
+ radial_mean = np.zeros(min(center_h, center_w))
595
+ for i in range(1, len(radial_mean)):
596
+ mask = (r == i)
597
+ if np.sum(mask) > 0:
598
+ radial_mean[i] = np.mean(magnitude[mask])
599
+
600
+ # 计算峰值特征
601
+ peaks, _ = stats.find_peaks(radial_mean)
602
+ if len(peaks) > 0:
603
+ features["freq_peak_count"] = len(peaks)
604
+ features["freq_peak_prominence"] = float(np.mean(radial_mean[peaks]))
605
+ except:
606
+ pass
607
+
608
+ def _extract_advanced_features(self, img_cv, features, image_id):
609
+ """Extract advanced features for AI detection"""
610
+ # 获取灰度图
611
+ gray = self.cache.get('gray')
612
+ if gray is None:
613
+ gray = self.get_grayscale(image_id)
614
+ if gray is None:
615
+ if len(img_cv.shape) == 3:
616
+ gray = cv2.cvtColor(img_cv, cv2.COLOR_BGR2GRAY)
617
+ else:
618
+ gray = img_cv
619
+ self.cache['gray'] = gray
620
+
621
+ # 小波变换特征
622
+ try:
623
+ # 如果图像太大,先降采样
624
+ if gray.shape[0] > 512 or gray.shape[1] > 512:
625
+ gray_small = cv2.resize(gray, (512, 512))
626
+ else:
627
+ gray_small = gray
628
+
629
+ # 执行小波变换
630
+ coeffs = pywt.dwt2(gray_small, 'haar')
631
+ cA, (cH, cV, cD) = coeffs
632
+
633
+ # 计算各子带的统计特征
634
+ features["wavelet_h_std"] = float(np.std(cH))
635
+ features["wavelet_v_std"] = float(np.std(cV))
636
+ features["wavelet_d_std"] = float(np.std(cD))
637
+
638
+ # 计算小波系数的熵
639
+ cH_hist, _ = np.histogram(cH.flatten(), bins=50)
640
+ cV_hist, _ = np.histogram(cV.flatten(), bins=50)
641
+ cD_hist, _ = np.histogram(cD.flatten(), bins=50)
642
+
643
+ features["wavelet_h_entropy"] = float(stats.entropy(cH_hist + 1e-10))
644
+ features["wavelet_v_entropy"] = float(stats.entropy(cV_hist + 1e-10))
645
+ features["wavelet_d_entropy"] = float(stats.entropy(cD_hist + 1e-10))
646
+ except:
647
+ pass
648
+
649
+ # DCT变换特征
650
+ try:
651
+ # 执行DCT变换
652
+ dct = cv2.dct(np.float32(gray_small))
653
+
654
+ # 计算DCT系数的统计特征
655
+ dct_std = np.std(dct)
656
+ features["dct_std"] = float(dct_std)
657
+
658
+ # 计算DCT系数的熵
659
+ dct_hist, _ = np.histogram(dct.flatten(), bins=50)
660
+ features["dct_entropy"] = float(stats.entropy(dct_hist + 1e-10))
661
+ except:
662
+ pass
663
+
664
+ # 图像质量评估
665
+ try:
666
+ # 计算梯度幅度图像
667
+ sobelx = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=3)
668
+ sobely = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=3)
669
+ gradient_magnitude = np.sqrt(sobelx**2 + sobely**2)
670
+
671
+ # 计算自然度指标
672
+ naturalness = np.mean(gradient_magnitude) / np.std(gradient_magnitude)
673
+ features["naturalness_index"] = float(naturalness)
674
+ except:
675
+ pass
676
  #############################################
677
+ # 特征分析与决策逻辑部分
678
  #############################################
679
 
680
+ # 基于图像类型的特征阈值
681
+ OPTIMIZED_THRESHOLDS = {
682
+ "lbp_entropy": {
683
+ "default": 2.0,
684
+ "portrait": 1.9,
685
+ "landscape": 2.2,
686
+ },
687
+ "freq_anisotropy": {
688
+ "default": 0.008,
689
+ "portrait": 0.007,
690
+ "landscape": 0.01,
691
+ },
692
+ "texture_correlation": {
693
+ "default": 0.95,
694
+ "portrait": 0.92,
695
+ "landscape": 0.95,
696
+ },
697
+ "horizontal_symmetry": {
698
+ "default": 0.85,
699
+ "portrait": 0.80,
700
+ "landscape": 0.85,
701
+ },
702
+ "vertical_symmetry": {
703
+ "default": 0.85,
704
+ "portrait": 0.80,
705
+ "landscape": 0.85,
706
+ },
707
+ "noise_spatial_std": {
708
+ "default": 0.3,
709
+ "portrait": 0.3,
710
+ "landscape": 0.4,
711
+ },
712
+ "freq_ratio": {
713
+ "default": 0.05,
714
+ "portrait": 0.05,
715
+ "landscape": 0.1,
716
+ },
717
+ "noise_spectrum_std": {
718
+ "default": 800,
719
+ "portrait": 800,
720
+ "landscape": 1000,
721
+ },
722
+ "color_entropy": {
723
+ "default": 3.5,
724
+ "portrait": 3.5,
725
+ "landscape": 4.0,
726
+ },
727
+ "lbp_uniformity": {
728
+ "default": 0.2,
729
+ "portrait": 0.2,
730
+ "landscape": 0.15,
731
+ },
732
+ "noise_channel_correlation": {
733
+ "default": 0.5,
734
+ "portrait": 0.5,
735
+ "landscape": 0.4,
736
+ },
737
+ "wavelet_h_entropy": {
738
+ "default": 3.0,
739
+ "portrait": 2.8,
740
+ "landscape": 3.2,
741
+ },
742
+ "dct_entropy": {
743
+ "default": 4.0,
744
+ "portrait": 3.8,
745
+ "landscape": 4.2,
746
+ },
747
+ "naturalness_index": {
748
+ "default": 1.5,
749
+ "portrait": 1.3,
750
+ "landscape": 1.7,
751
+ }
752
+ }
753
 
754
+ # 特征重要性权重
755
+ FEATURE_IMPORTANCE = {
756
+ "lbp_entropy": 0.15,
757
+ "freq_anisotropy": 0.15,
758
+ "texture_correlation": 0.08,
759
+ "horizontal_symmetry": 0.03,
760
+ "vertical_symmetry": 0.03,
761
+ "noise_spatial_std": 0.08,
762
+ "freq_ratio": 0.05,
763
+ "noise_spectrum_std": 0.05,
764
+ "color_entropy": 0.08,
765
+ "lbp_uniformity": 0.05,
766
+ "noise_channel_correlation": 0.05,
767
+ "wavelet_h_entropy": 0.07,
768
+ "dct_entropy": 0.08,
769
+ "naturalness_index": 0.05
770
  }
771
 
772
+ def get_threshold(feature_name, image_type="default"):
773
+ """Get the appropriate threshold for a feature based on image type"""
774
+ if feature_name not in OPTIMIZED_THRESHOLDS:
775
+ return None
776
+
777
+ return OPTIMIZED_THRESHOLDS[feature_name].get(image_type,
778
+ OPTIMIZED_THRESHOLDS[feature_name]["default"])
779
+
780
  def check_ai_specific_features(image_features):
781
+ """Enhanced check for AI-generated image features"""
782
  ai_score = 0
783
  ai_signs = []
784
 
785
+ # 获取图像类型
786
+ image_type = image_features.get("image_type", "default")
787
+
788
+ # 处理每个特征
789
+ for feature_name, importance in FEATURE_IMPORTANCE.items():
790
  if feature_name not in image_features:
791
  continue
792
 
793
  value = image_features[feature_name]
794
+ threshold = get_threshold(feature_name, image_type)
795
+
796
+ if threshold is None:
797
+ continue
798
 
799
  # 不同特征有不同的比较逻辑
800
+ feature_score = 0
801
+
802
+ # 低值表示AI生成的特征
803
+ if feature_name in ["lbp_entropy", "freq_anisotropy", "noise_spatial_std",
804
+ "freq_ratio", "noise_spectrum_std", "color_entropy",
805
+ "wavelet_h_entropy", "dct_entropy", "naturalness_index"]:
806
  if value < threshold:
807
+ feature_score = min(1.0, (threshold - value) / threshold * 2)
808
+ if feature_score > 0.5:
809
+ ai_signs.append(f"{feature_name} 异常低 ({value:.2f})")
810
+
811
+ # 高值表示AI生成的特征
812
+ elif feature_name in ["texture_correlation", "horizontal_symmetry", "vertical_symmetry",
813
+ "lbp_uniformity", "noise_channel_correlation"]:
814
  if value > threshold:
815
+ feature_score = min(1.0, (value - threshold) / (1 - threshold) * 2)
816
+ if feature_score > 0.5:
817
+ ai_signs.append(f"{feature_name} 异常高 ({value:.2f})")
818
+
819
+ # 累加加权分数
820
+ ai_score += feature_score * importance
821
 
822
  # 计算检测到多少关键特征
823
  critical_count = len(ai_signs)
824
  if critical_count >= 5:
825
+ ai_score = max(ai_score, 0.8) # 更保守,从0.9改为0.8
826
  elif critical_count >= 3:
827
+ ai_score = max(ai_score, 0.6) # 更保守,从0.7改为0.6
828
 
829
  return min(ai_score, 1.0), ai_signs
830
 
 
833
  beauty_score = 0
834
  beauty_signs = []
835
 
836
+ # 获取图像类型
837
+ image_type = image_features.get("image_type", "default")
838
+
839
  # 只检查最重要的美颜滤镜指标
840
  if "face_skin_std" in image_features:
841
+ threshold = 15 if image_type == "portrait" else 20
842
+ if image_features["face_skin_std"] < threshold:
843
  beauty_score += 0.3
844
  beauty_signs.append("皮肤质感过于均匀")
845
 
846
  if "edge_density" in image_features:
847
+ threshold = 0.03 if image_type == "portrait" else 0.04
848
+ if image_features["edge_density"] < threshold:
849
  beauty_score += 0.2
850
  beauty_signs.append("边缘过于平滑")
851
 
852
  if "noise_level" in image_features:
853
+ threshold = 1.0 if image_type == "portrait" else 1.5
854
+ if image_features["noise_level"] < threshold:
855
  beauty_score += 0.2
856
  beauty_signs.append("噪点异常少")
857
 
858
+ if "texture_homogeneity" in image_features:
859
+ threshold = 0.5 if image_type == "portrait" else 0.4
860
+ if image_features["texture_homogeneity"] > threshold:
861
+ beauty_score += 0.2
862
+ beauty_signs.append("纹理过于均匀")
863
+
864
  return min(beauty_score, 1.0), beauty_signs
865
 
866
  def detect_photoshop_signs(image_features):
 
868
  ps_score = 0
869
  ps_signs = []
870
 
871
+ # 获取图像类型
872
+ image_type = image_features.get("image_type", "default")
873
+
874
  # 只检查最重要的PS指标
875
  if "texture_homogeneity" in image_features:
876
+ threshold = 0.4 if image_type == "portrait" else 0.35
877
+ if image_features["texture_homogeneity"] > threshold:
878
  ps_score += 0.2
879
  ps_signs.append("皮肤质感过于均匀")
880
 
881
  if "edge_density" in image_features:
882
+ threshold = 0.01 if image_type == "portrait" else 0.015
883
+ if image_features["edge_density"] < threshold:
884
  ps_score += 0.2
885
  ps_signs.append("边缘过于平滑")
886
 
887
  if "color_std" in image_features:
888
+ threshold = 50 if image_type == "portrait" else 40
889
+ if image_features["color_std"] > threshold:
890
  ps_score += 0.2
891
  ps_signs.append("颜色分布极不自然")
892
 
893
+ if "noise_spatial_std" in image_features:
894
+ threshold = 1.0 if image_type == "portrait" else 1.2
895
+ if image_features["noise_spatial_std"] > threshold:
896
+ ps_score += 0.2
897
+ ps_signs.append("噪点分布不均匀")
898
+
899
  return min(ps_score, 1.0), ps_signs
900
 
901
+ def calculate_model_consistency(model_results):
902
+ """Calculate the consistency between model predictions"""
903
+ if not model_results:
904
+ return 0.0
905
+
906
+ # 提取AI概率
907
+ probabilities = [result.get("ai_probability", 0.5) for result in model_results.values()
908
+ if "error" not in result]
909
+
910
+ if not probabilities:
911
+ return 0.0
912
+
913
+ # 计算方差作为一致性度量
914
+ variance = np.var(probabilities)
915
+
916
+ # 方差越小,一致性越高
917
+ consistency = max(0.0, 1.0 - variance * 5) # 缩放以获得合理的一致性分数
918
 
919
+ return consistency
920
+
921
+ def get_detailed_analysis(ai_probability, ps_score, beauty_score, ps_signs, ai_signs, beauty_signs,
922
+ valid_models_count, ai_feature_score, model_consistency):
923
+ """Provide detailed analysis with two-level classification and confidence assessment"""
924
+
925
+ # 根据模型数量和一致性调整置信度
926
+ if model_consistency > 0.8 and valid_models_count >= 2:
927
  confidence_prefix = "极高置信度:"
928
+ elif model_consistency > 0.6 and valid_models_count >= 2:
929
  confidence_prefix = "高置信度:"
930
+ elif valid_models_count >= 1:
931
  confidence_prefix = "中等置信度:"
932
+ else:
933
+ confidence_prefix = "低置信度:"
934
 
935
  # 计算编辑分数(在所有路径中都需要)
936
  combined_edit_score = max(ps_score, beauty_score)
937
 
938
+ # 当模型和特征分析严重不一致时降低置信度
939
+ if abs(ai_probability - ai_feature_score) > 0.4:
940
+ confidence_prefix = "低置信度:"
941
+ explanation = "(模型预测和特征分析结果存在较大差异)"
942
+ else:
943
+ explanation = ""
944
+
945
  # 处理特征与模型判断不一致的情况
946
+ if ai_feature_score > 0.8 and ai_probability > 0.4: # 添加条件
947
+ ai_probability = max(ai_probability, 0.7) # 更保守,从0.8改为0.7
948
+ category = confidence_prefix + "AI生成图像" + explanation
949
+ description = "图像很可能是由AI完全生成,几乎没有真人照片的特征。"
950
  main_category = "AI生成"
951
+ elif ai_feature_score > 0.6 and ai_probability > 0.3: # 添加条件
952
+ ai_probability = max(ai_probability, 0.6) # 更保守,从0.7改为0.6
953
 
954
  # 第一级分类:AI vs 真实
955
  if ai_probability > 0.6:
956
+ category = confidence_prefix + "AI生成图像" + explanation
957
  description = "图像很可能是由AI完全生成,几乎没有真人照片的特征。"
958
  main_category = "AI生成"
959
  else:
960
  # 第二级分类:素人 vs 修图
961
  if combined_edit_score > 0.5:
962
+ category = confidence_prefix + "真人照片,修图痕迹明显" + explanation
963
  description = "图像基本是真人照片,但经过了明显的后期处理或美颜,修饰痕迹明显。"
964
  main_category = "真人照片-修图明显"
965
  else:
966
+ category = confidence_prefix + "真实素人照片" + explanation
967
  description = "图像很可能是未经大量处理的真人照片,保留了自然的细节和特征。"
968
  main_category = "真人照片-素人"
969
 
970
+ # 处理边界情况 - 添加"不确定"类别
971
+ if 0.4 < ai_probability < 0.6 and abs(ai_probability - ai_feature_score) > 0.3:
972
+ category = "无法确定" + explanation
973
+ description = "系统无法确定该图像是AI生成还是真实照片。模型预测和特征分析结果不一致,需要人工判断。"
974
+ main_category = "无法确定"
975
+
976
+ # 处理边界情况 - AI生成与高度修图
977
+ elif ai_probability > 0.45 and combined_edit_score > 0.7:
978
+ category = confidence_prefix + "真人照片,修图痕迹明显(也可能是AI生成)" + explanation
979
  description = "图像可能是真人照片经过大量后期处理,也可能是AI生成图像。由于现代AI技术与高度修图效果相似,难以完全区分。"
980
  main_category = "真人照片-修图明显"
981
 
 
986
 
987
  return category, description, ps_details, ai_details, beauty_details, main_category
988
  def detect_ai_image(image):
989
+ """Enhanced main detection function with two-stage detection and improved decision logic"""
990
  if image is None:
991
  return {"error": "未提供图像"}
992
 
993
  start_time = time.time()
994
+ image_id = str(uuid.uuid4()) # 为图像生成唯一ID,用于反馈收集
995
+
996
+ # 初始化管理器
997
+ model_manager = EnhancedModelManager()
998
+ feature_extractor = EnhancedFeatureExtractor()
999
 
1000
+ # 第一阶段:快速特征分析
1001
+ # 确定是否需要降采样
1002
+ downscale_factor = 1.0
1003
+ if image.width * image.height > 1024 * 1024: # 对于大于1MP的图像
1004
+ downscale_factor = min(1.0, 1024 * 1024 / (image.width * image.height))
1005
+
1006
+ # 提取特征
1007
+ feature_extractor.clear_cache() # 清除之前运行的缓存
1008
+ image_features = feature_extractor.analyze_image_features(image, downscale_factor)
1009
+
1010
+ # 快速特征分析
1011
+ ai_feature_score, ai_signs = check_ai_specific_features(image_features)
1012
+
1013
+ # 第二阶段:模型分析
1014
  results = {}
1015
  valid_models = 0
1016
  weighted_ai_probability = 0
 
1035
  else:
1036
  return {"error": "所有模型加载失败"}
1037
 
1038
+ # 计算模型一致性
1039
+ model_consistency = calculate_model_consistency(results)
 
 
 
 
 
 
 
1040
 
1041
+ # 分析PS和美颜痕迹
 
1042
  ps_score, ps_signs = detect_photoshop_signs(image_features)
1043
  beauty_score, beauty_signs = detect_beauty_filter_signs(image_features)
1044
 
1045
+ # 协同决策:结合模型预测和特征分析
1046
  adjusted_probability = final_ai_probability
1047
 
1048
+ # 根据模型一致性调整特征分析的影响
1049
+ if model_consistency > 0.7:
1050
+ # 模型一致性高,增加模型预测的权重
1051
+ if ai_feature_score > 0.8 and final_ai_probability > 0.4:
1052
+ adjusted_probability = 0.7 * final_ai_probability + 0.3 * ai_feature_score
1053
+ elif ai_feature_score > 0.6 and final_ai_probability > 0.3:
1054
+ adjusted_probability = 0.6 * final_ai_probability + 0.4 * ai_feature_score
1055
+ else:
1056
+ adjusted_probability = 0.8 * final_ai_probability + 0.2 * ai_feature_score
1057
+ else:
1058
+ # 模型一致性低,增加特征分析的权重
1059
+ if ai_feature_score > 0.8 and final_ai_probability > 0.4:
1060
+ adjusted_probability = 0.4 * final_ai_probability + 0.6 * ai_feature_score
1061
+ elif ai_feature_score > 0.6 and final_ai_probability > 0.3:
1062
+ adjusted_probability = 0.5 * final_ai_probability + 0.5 * ai_feature_score
1063
+ else:
1064
+ adjusted_probability = 0.6 * final_ai_probability + 0.4 * ai_feature_score
1065
 
1066
  # 检查关键特征
1067
  key_ai_features_count = 0
1068
 
1069
  # LBP熵(微观纹理分析)
1070
+ if "lbp_entropy" in image_features:
1071
+ threshold = get_threshold("lbp_entropy", image_features.get("image_type", "default"))
1072
+ if image_features["lbp_entropy"] < threshold:
1073
+ key_ai_features_count += 1
1074
 
1075
  # 频率各向异性
1076
+ if "freq_anisotropy" in image_features:
1077
+ threshold = get_threshold("freq_anisotropy", image_features.get("image_type", "default"))
1078
+ if image_features["freq_anisotropy"] < threshold:
1079
+ key_ai_features_count += 1
1080
 
1081
+ # 小波熵
1082
+ if "wavelet_h_entropy" in image_features:
1083
+ threshold = get_threshold("wavelet_h_entropy", image_features.get("image_type", "default"))
1084
+ if image_features["wavelet_h_entropy"] < threshold:
1085
+ key_ai_features_count += 1
1086
 
1087
+ # 多个关键特征强烈表明AI生成,但需要模型支持
1088
+ if key_ai_features_count >= 2 and final_ai_probability > 0.3:
1089
+ adjusted_probability = max(adjusted_probability, 0.6) # 更保守,从0.7改为0.6
1090
 
1091
  # 确保概率在有效范围内
1092
  adjusted_probability = min(1.0, max(0.0, adjusted_probability))
1093
 
1094
+ # 获取详细分析
1095
  category, description, ps_details, ai_details, beauty_details, main_category = get_detailed_analysis(
1096
  adjusted_probability, ps_score, beauty_score, ps_signs, ai_signs, beauty_signs,
1097
+ valid_models, ai_feature_score, model_consistency
1098
  )
1099
 
1100
  # 构建最终结果
1101
  processing_time = time.time() - start_time
1102
 
1103
  final_result = {
1104
+ "image_id": image_id,
1105
  "ai_probability": adjusted_probability,
1106
  "original_ai_probability": final_ai_probability,
1107
  "ps_score": ps_score,
1108
  "beauty_score": beauty_score,
1109
  "ai_feature_score": ai_feature_score,
1110
+ "model_consistency": model_consistency,
1111
  "category": category,
1112
  "main_category": main_category,
1113
  "description": description,
 
1117
  "processing_time": f"{processing_time:.2f} seconds",
1118
  "individual_model_results": results,
1119
  # 只包含最重要的特征以减少响应大小
1120
+ "key_features": {k: image_features[k] for k in FEATURE_IMPORTANCE if k in image_features}
1121
  }
1122
 
1123
+ # 保存结果用于后续分析
1124
+ try:
1125
+ with open(f"{FEEDBACK_DIR}/{image_id}.json", "w") as f:
1126
+ json.dump(final_result, f)
1127
+ except:
1128
+ pass
1129
+
1130
  # 返回两个值:JSON结果和标签数据
1131
  label_data = {main_category: 1.0}
1132
  return final_result, label_data
1133
 
1134
+ def save_user_feedback(image_id, user_feedback):
1135
+ """Save user feedback for continuous learning"""
1136
+ if not image_id:
1137
+ return {"status": "error", "message": "未提供图像ID"}
1138
+
1139
+ try:
1140
+ # 读取原始结果
1141
+ result_path = f"{FEEDBACK_DIR}/{image_id}.json"
1142
+ if not os.path.exists(result_path):
1143
+ return {"status": "error", "message": "找不到对应的图像分析结果"}
1144
+
1145
+ with open(result_path, "r") as f:
1146
+ original_result = json.load(f)
1147
+
1148
+ # 添加用户反馈
1149
+ feedback_data = {
1150
+ "image_id": image_id,
1151
+ "original_result": original_result,
1152
+ "user_feedback": user_feedback,
1153
+ "timestamp": datetime.now().isoformat()
1154
+ }
1155
+
1156
+ # 保存反馈
1157
+ feedback_path = f"{FEEDBACK_DIR}/{image_id}_feedback.json"
1158
+ with open(feedback_path, "w") as f:
1159
+ json.dump(feedback_data, f)
1160
+
1161
+ return {"status": "success", "message": "反馈已保存,感谢您的贡献!"}
1162
+ except Exception as e:
1163
+ return {"status": "error", "message": f"保存反馈时出错: {str(e)}"}
1164
+
1165
+ #############################################
1166
+ # Gradio界面部分
1167
+ #############################################
1168
+
1169
+ def process_image_and_show_results(image):
1170
+ """Process image and format results for Gradio interface"""
1171
+ if image is None:
1172
+ return {"error": "请上传图像"}, None, "未检测"
1173
+
1174
+ try:
1175
+ result, label = detect_ai_image(image)
1176
+ return result, result["image_id"], result["main_category"]
1177
+ except Exception as e:
1178
+ return {"error": f"处理图像时出错: {str(e)}"}, None, "错误"
1179
+
1180
+ def submit_feedback(image_id, correct_classification, comments):
1181
+ """Submit user feedback"""
1182
+ if not image_id:
1183
+ return "请先上传图像进行分析"
1184
+
1185
+ feedback = {
1186
+ "correct_classification": correct_classification,
1187
+ "comments": comments
1188
+ }
1189
+
1190
+ result = save_user_feedback(image_id, feedback)
1191
+ return result["message"]
1192
+
1193
  # 创建Gradio界面
1194
+ with gr.Blocks(title="增强型AI图像检测系统") as iface:
1195
+ gr.Markdown("# 增强型AI图像检测系统")
1196
+ gr.Markdown("上传图像,系统将分析该图像是AI生成还是真实照片")
1197
+
1198
+ with gr.Row():
1199
+ with gr.Column(scale=1):
1200
+ input_image = gr.Image(type="pil", label="上传图像")
1201
+ analyze_btn = gr.Button("分析图像", variant="primary")
1202
+
1203
+ with gr.Column(scale=2):
1204
+ result_json = gr.JSON(label="详细分析结果")
1205
+ image_id_output = gr.Textbox(label="图像ID", visible=False)
1206
+ result_label = gr.Label(label="主要分类")
1207
+
1208
+ gr.Markdown("## 用户反馈")
1209
+ gr.Markdown("您认为上述分类结果是否正确?您的反馈将帮助我们改进系统。")
1210
+
1211
+ with gr.Row():
1212
+ correct_classification = gr.Radio(
1213
+ ["正确", "错误 - 这是AI生成图像", "错误 - 这是真实照片", "不确定"],
1214
+ label="分类结果是否正确"
1215
+ )
1216
+ comments = gr.Textbox(label="其他评论(可选)")
1217
+
1218
+ feedback_btn = gr.Button("提交反馈")
1219
+ feedback_result = gr.Textbox(label="反馈结果")
1220
+
1221
+ # 设置事件
1222
+ analyze_btn.click(
1223
+ process_image_and_show_results,
1224
+ inputs=[input_image],
1225
+ outputs=[result_json, image_id_output, result_label]
1226
+ )
1227
+
1228
+ feedback_btn.click(
1229
+ submit_feedback,
1230
+ inputs=[image_id_output, correct_classification, comments],
1231
+ outputs=[feedback_result]
1232
+ )
1233
 
1234
  # 启动应用
1235
+ if __name__ == "__main__":
1236
+ iface.launch(share=True)