✨ feat(llm): 增强文案反 AI 检测能力并优化图片后处理
- 在系统提示词中新增【反AI检测规则】章节,包含句子长度、逻辑跳跃、标点符号等8项防检测措施 - 为所有SD模型提示词指南添加通用反AI检测技巧,强调真实手机拍摄风格 - 深度重构 `_humanize_content` 方法,新增8层真人化处理:替换书面表达、打散句子长度、随机添加口语元素、模拟手机打字标点习惯 - 增强 `_humanize` 方法,去除更多AI前缀,随机化标点,限制表情符号堆叠 - 在 `sd_service.py` 新增 `anti_detect_postprocess` 图片后处理管线,包含元数据剥离、随机裁剪、色彩微扰、不均匀噪声、JPEG压缩回环等7步处理 - 所有图片生成后自动经过反检测处理,输出格式统一为JPEG以模拟真实手机照片 - 更新 `main.py` 中的图片保存逻辑,统一使用JPEG格式并确保RGB模式转换
This commit is contained in:
parent
156a18ae0c
commit
0c91c00dcf
178
llm_service.py
178
llm_service.py
@ -5,6 +5,7 @@ LLM 服务模块
|
||||
import requests
|
||||
import json
|
||||
import re
|
||||
import random
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@ -44,6 +45,17 @@ PROMPT_COPYWRITING = """
|
||||
❌ "小伙伴们" "宝子们" 等过度热情的称呼(偶尔一次可以)
|
||||
8. 结尾加 5-8 个相关话题标签(#)
|
||||
|
||||
【!!极其重要 — 反AI检测规则!!】:
|
||||
你的文案会被AI检测工具审查,必须通过以下防检测措施:
|
||||
1. 句子长度必须参差不齐:有的句子就3-5个字,有的20多字,绝对不能每句都差不多长
|
||||
2. 不要所有段落逻辑都很通顺——真人写东西会突然跑题、突然想起另一件事
|
||||
3. 偶尔犯一点小"不规范":比如某句话没写完就换话题了、用了个不太准确的词
|
||||
4. 标点符号不要太规范:真人用手机打字经常漏掉逗号、句号不加,偶尔连续用两个问号??
|
||||
5. 用词不要太"正确":真人会说"巨好用" "绝绝子" "无敌了" 而不是"非常好用" "十分出色"
|
||||
6. 避免完美的逻辑链条:不要每段都工工整整地推进论点,真人笔记是跳跃式的
|
||||
7. 偶尔口语化到"学渣"程度:"就 很那个 你懂的" "属于是" "多少有点" "怎么说呢"
|
||||
8. 绝对不要用"然而" "此外" "因此" "尽管" "虽然...但是..."这些书面连接词
|
||||
|
||||
【绘图 Prompt】:
|
||||
{sd_prompt_guide}
|
||||
|
||||
@ -278,7 +290,7 @@ class LLMService:
|
||||
|
||||
@staticmethod
|
||||
def get_sd_prompt_guide(sd_model_name: str = None) -> str:
|
||||
"""根据当前 SD 模型生成 LLM 使用的绘图 Prompt 指南"""
|
||||
"""根据当前 SD 模型生成 LLM 使用的绘图 Prompt 指南(含反 AI 检测指导)"""
|
||||
from sd_service import SD_MODEL_PROFILES, detect_model_profile
|
||||
|
||||
key = detect_model_profile(sd_model_name) if sd_model_name else "juggernautXL"
|
||||
@ -287,6 +299,16 @@ class LLMService:
|
||||
display = profile.get("display_name", key)
|
||||
desc = profile.get("description", "")
|
||||
|
||||
# 通用反 AI 检测 prompt 技巧
|
||||
anti_detect_tips = (
|
||||
"\n\n【重要 - 反AI检测】生成的图片需要尽量像真实手机拍摄:\n"
|
||||
"- 在 prompt 中加入真实感关键词:shot on iPhone, casual snapshot, real life, candid photo\n"
|
||||
"- 加入微小不完美:slight motion blur, natural background, everyday environment\n"
|
||||
"- 避免过度完美的构图词:不要用 perfect composition, symmetrical, flawless 等\n"
|
||||
"- 光线自然化:用 natural daylight, indoor ambient light, window light 而非 studio lighting\n"
|
||||
"- 模拟手机拍照特征:phone camera, slightly overexposed, casual angle, not centered\n"
|
||||
)
|
||||
|
||||
if key == "majicmixRealistic":
|
||||
return (
|
||||
f"生成 Stable Diffusion 英文提示词,当前使用模型: {display} ({desc})\n"
|
||||
@ -298,6 +320,7 @@ class LLMService:
|
||||
"- 非常适合:自拍、穿搭展示、美妆效果、生活日常、闺蜜合照风格\n"
|
||||
"- 画面要有「朋友圈精选照片」的感觉,自然不做作\n"
|
||||
"- 用英文逗号分隔"
|
||||
+ anti_detect_tips
|
||||
)
|
||||
elif key == "realisticVision":
|
||||
return (
|
||||
@ -311,6 +334,7 @@ class LLMService:
|
||||
"- 非常适合:街拍、纪实风、旅行照、真实场景、有故事感的画面\n"
|
||||
"- 画面要有「专业摄影师抓拍」的质感,保留真实皮肤纹理\n"
|
||||
"- 用英文逗号分隔"
|
||||
+ anti_detect_tips
|
||||
)
|
||||
else: # juggernautXL (SDXL)
|
||||
return (
|
||||
@ -324,6 +348,7 @@ class LLMService:
|
||||
"- 非常适合:商业摄影、时尚大片、复杂光影场景、杂志封面风格\n"
|
||||
"- 画面要有「电影画面/杂志大片」的高级感\n"
|
||||
"- 用英文逗号分隔"
|
||||
+ anti_detect_tips
|
||||
)
|
||||
|
||||
def _chat(self, system_prompt: str, user_message: str,
|
||||
@ -603,9 +628,10 @@ class LLMService:
|
||||
|
||||
@staticmethod
|
||||
def _humanize_content(text: str) -> str:
|
||||
"""后处理: 去除长文案中的 AI 书面痕迹"""
|
||||
"""后处理: 深度去除 AI 书面痕迹,模拟真人手机打字风格"""
|
||||
t = text
|
||||
# 替换过于书面化的表达
|
||||
|
||||
# ========== 第一层: 替换过于书面化/AI化的表达 ==========
|
||||
ai_phrases = {
|
||||
"值得一提的是": "对了",
|
||||
"需要注意的是": "不过要注意",
|
||||
@ -623,37 +649,171 @@ class LLMService:
|
||||
"接下来让我们": "",
|
||||
"话不多说": "",
|
||||
"废话不多说": "",
|
||||
"小伙伴们": "姐妹们",
|
||||
"下面我来": "",
|
||||
"让我来": "",
|
||||
"首先我要说": "先说",
|
||||
"我认为": "我觉得",
|
||||
"我相信": "我觉得",
|
||||
"事实上": "其实",
|
||||
"实际上": "其实",
|
||||
"毫无疑问": "",
|
||||
"不可否认": "",
|
||||
"客观来说": "",
|
||||
"坦白说": "",
|
||||
"具体而言": "就是",
|
||||
"简而言之": "就是说",
|
||||
"换句话说": "就是",
|
||||
"归根结底": "说白了",
|
||||
"由此可见": "",
|
||||
"正如我所说": "",
|
||||
"正如前文所述": "",
|
||||
"在我看来": "我觉得",
|
||||
"从某种程度上说": "",
|
||||
"在一定程度上": "",
|
||||
"非常值得推荐": "真的可以试试",
|
||||
"强烈推荐": "真心推荐",
|
||||
"性价比极高": "性价比很高",
|
||||
"给大家安利": "安利",
|
||||
"为大家推荐": "推荐",
|
||||
"希望对大家有所帮助": "",
|
||||
"希望能帮到大家": "",
|
||||
"以上就是": "",
|
||||
"感谢阅读": "",
|
||||
"感谢大家的阅读": "",
|
||||
}
|
||||
for old, new in ai_phrases.items():
|
||||
t = t.replace(old, new)
|
||||
# 去掉 "首先" "其次" "最后" 的分点罗列感
|
||||
|
||||
# ========== 第二层: 去掉分点罗列感 ==========
|
||||
t = re.sub(r'(?m)^首先[,,::\s]*', '', t)
|
||||
t = re.sub(r'(?m)^其次[,,::\s]*', '', t)
|
||||
t = re.sub(r'(?m)^最后[,,::\s]*', '', t)
|
||||
t = re.sub(r'(?m)^再者[,,::\s]*', '', t)
|
||||
# 去掉AI常见的空洞开头
|
||||
for prefix in ["嗨大家好!", "嗨,大家好!", "大家好,", "大家好!", "哈喽大家好!"]:
|
||||
t = re.sub(r'(?m)^另外[,,::\s]*', '', t)
|
||||
# 去序号: "1. " "2、" "①" 等
|
||||
t = re.sub(r'(?m)^[①②③④⑤⑥⑦⑧⑨⑩]\s*', '', t)
|
||||
t = re.sub(r'(?m)^[1-9][.、))]\s*', '', t)
|
||||
|
||||
# ========== 第三层: 去掉AI常见的空洞开头 ==========
|
||||
for prefix in ["嗨大家好!", "嗨,大家好!", "大家好,", "大家好!",
|
||||
"哈喽大家好!", "Hello大家好!", "嗨~", "hey~",
|
||||
"各位姐妹大家好!", "各位宝子们好!"]:
|
||||
if t.startswith(prefix):
|
||||
t = t[len(prefix):].strip()
|
||||
|
||||
# ========== 第四层: 标点符号真人化 ==========
|
||||
# AI 特征: 每句话都有完整标点 → 真人经常不加标点或只用逗号
|
||||
sentences = t.split('\n')
|
||||
humanized_lines = []
|
||||
for line in sentences:
|
||||
if not line.strip():
|
||||
humanized_lines.append(line)
|
||||
continue
|
||||
# 随机去掉句末句号 (真人经常不打句号)
|
||||
if line.rstrip().endswith('。') and random.random() < 0.35:
|
||||
line = line.rstrip()[:-1]
|
||||
# 随机把部分逗号替换成空格或什么都不加 (模拟打字不加标点)
|
||||
if random.random() < 0.15:
|
||||
# 只替换一个逗号
|
||||
comma_positions = [m.start() for m in re.finditer(r'[,,]', line)]
|
||||
if comma_positions:
|
||||
pos = random.choice(comma_positions)
|
||||
line = line[:pos] + ' ' + line[pos+1:]
|
||||
humanized_lines.append(line)
|
||||
t = '\n'.join(humanized_lines)
|
||||
|
||||
# ========== 第五层: 随机添加真人口语化元素 ==========
|
||||
# 在段落开头随机插入口语衔接词
|
||||
oral_connectors = [
|
||||
"对了 ", "哦对 ", "话说 ", "然后 ", "就是说 ", "emmm ", "嗯 ",
|
||||
"说真的 ", "不是 ", "离谱的是 ", "我发现 ",
|
||||
]
|
||||
paragraphs = t.split('\n\n')
|
||||
if len(paragraphs) > 2:
|
||||
# 在中间段落随机加1-2个口语衔接词
|
||||
inject_count = random.randint(1, min(2, len(paragraphs) - 2))
|
||||
inject_indices = random.sample(range(1, len(paragraphs)), inject_count)
|
||||
for idx in inject_indices:
|
||||
if paragraphs[idx].strip() and not any(paragraphs[idx].strip().startswith(c.strip()) for c in oral_connectors):
|
||||
connector = random.choice(oral_connectors)
|
||||
paragraphs[idx] = connector + paragraphs[idx].lstrip()
|
||||
t = '\n\n'.join(paragraphs)
|
||||
|
||||
# ========== 第六层: 句子长度打散 ==========
|
||||
# AI 特征: 句子长度高度均匀 → 真人笔记长短参差不齐
|
||||
# 随机把一些长句用换行打散
|
||||
lines = t.split('\n')
|
||||
final_lines = []
|
||||
for line in lines:
|
||||
# 超过60字的行, 随机在一个位置断句
|
||||
if len(line) > 60 and random.random() < 0.3:
|
||||
# 找到中间附近的标点位置断句
|
||||
mid = len(line) // 2
|
||||
best_pos = -1
|
||||
for offset in range(0, mid):
|
||||
for check_pos in [mid + offset, mid - offset]:
|
||||
if 0 < check_pos < len(line) and line[check_pos] in ',。!?、,':
|
||||
best_pos = check_pos
|
||||
break
|
||||
if best_pos > 0:
|
||||
break
|
||||
if best_pos > 0:
|
||||
final_lines.append(line[:best_pos + 1])
|
||||
final_lines.append(line[best_pos + 1:].lstrip())
|
||||
continue
|
||||
final_lines.append(line)
|
||||
t = '\n'.join(final_lines)
|
||||
|
||||
# ========== 第七层: 随机注入微小不完美 ==========
|
||||
# 真人打字偶尔有重复字、多余空格等
|
||||
if random.random() < 0.2:
|
||||
# 随机在某处加一个波浪号或省略号
|
||||
insert_chars = ['~', '...', '~', '..']
|
||||
lines = t.split('\n')
|
||||
if lines:
|
||||
target = random.randint(0, len(lines) - 1)
|
||||
if lines[target].rstrip() and not lines[target].rstrip()[-1] in '~~.。!?!?':
|
||||
lines[target] = lines[target].rstrip() + random.choice(insert_chars)
|
||||
t = '\n'.join(lines)
|
||||
|
||||
# ========== 第八层: 清理 ==========
|
||||
# 去掉连续3个以上的 emoji
|
||||
t = re.sub(r'([\U0001F600-\U0001F9FF\u2600-\u27BF])\1{2,}', r'\1\1', t)
|
||||
# 清理多余空行
|
||||
t = re.sub(r'\n{3,}', '\n\n', t)
|
||||
# 清理行首多余空格 (手机打字不会缩进)
|
||||
t = re.sub(r'(?m)^[ \t]+', '', t)
|
||||
|
||||
return t.strip()
|
||||
|
||||
@staticmethod
|
||||
def _humanize(text: str) -> str:
|
||||
"""后处理: 去除 AI 输出中常见的非人类痕迹"""
|
||||
"""后处理: 深度去除 AI 评论/回复中的非人类痕迹"""
|
||||
t = text.strip()
|
||||
# 去掉前后引号包裹
|
||||
if (t.startswith('"') and t.endswith('"')) or (t.startswith("'") and t.endswith("'")):
|
||||
t = t[1:-1].strip()
|
||||
# 去掉 AI 常见的前缀
|
||||
for prefix in ["回复:", "回复:", "评论:", "评论:", "以下是", "好的,"]:
|
||||
for prefix in ["回复:", "回复:", "评论:", "评论:", "以下是", "好的,",
|
||||
"当然,", "当然!", "谢谢你的", "感谢你的", "好的!",
|
||||
"嗯,"]:
|
||||
if t.startswith(prefix):
|
||||
t = t[len(prefix):].strip()
|
||||
# 去掉末尾多余的句号(真人评论很少用句号结尾)
|
||||
if t.endswith("。"):
|
||||
t = t[:-1]
|
||||
# 去掉末尾的"哦" "呢" 堆叠 (AI 常见)
|
||||
t = re.sub(r'[哦呢呀哈]{2,}$', lambda m: m.group()[0], t)
|
||||
# 替换过于完整规范的标点为口语化
|
||||
if random.random() < 0.25 and ',' in t:
|
||||
# 随机去掉一个逗号
|
||||
comma_pos = [m.start() for m in re.finditer(',', t)]
|
||||
if comma_pos:
|
||||
pos = random.choice(comma_pos)
|
||||
t = t[:pos] + ' ' + t[pos+1:]
|
||||
# 随机去掉末尾感叹号(真人不是每句都加!)
|
||||
if t.endswith('!') and random.random() < 0.3:
|
||||
t = t[:-1]
|
||||
# 限制连续 emoji(最多2个)
|
||||
t = re.sub(r'([\U0001F600-\U0001F9FF\u2600-\u27BF])\1{2,}', r'\1\1', t)
|
||||
return t
|
||||
|
||||
18
main.py
18
main.py
@ -386,9 +386,11 @@ def one_click_export(title, content, images):
|
||||
saved_paths = []
|
||||
if images:
|
||||
for idx, img in enumerate(images):
|
||||
path = os.path.join(folder_path, f"图{idx+1}.png")
|
||||
path = os.path.join(folder_path, f"图{idx+1}.jpg")
|
||||
if isinstance(img, Image.Image):
|
||||
img.save(path)
|
||||
if img.mode != "RGB":
|
||||
img = img.convert("RGB")
|
||||
img.save(path, format="JPEG", quality=95)
|
||||
saved_paths.append(os.path.abspath(path))
|
||||
|
||||
# 尝试打开文件夹
|
||||
@ -422,8 +424,10 @@ def publish_to_xhs(title, content, tags_str, images, local_images, mcp_url, sche
|
||||
os.makedirs(temp_dir, exist_ok=True)
|
||||
for idx, img in enumerate(images):
|
||||
if isinstance(img, Image.Image):
|
||||
path = os.path.abspath(os.path.join(temp_dir, f"ai_{idx}.png"))
|
||||
img.save(path)
|
||||
path = os.path.abspath(os.path.join(temp_dir, f"ai_{idx}.jpg"))
|
||||
if img.mode != "RGB":
|
||||
img = img.convert("RGB")
|
||||
img.save(path, format="JPEG", quality=95)
|
||||
image_paths.append(path)
|
||||
|
||||
# 添加本地上传的图片
|
||||
@ -2082,8 +2086,10 @@ def auto_publish_once(topics_str, mcp_url, sd_url_val, sd_model_name, model, per
|
||||
image_paths = []
|
||||
for idx, img in enumerate(images):
|
||||
if isinstance(img, Image.Image):
|
||||
path = os.path.abspath(os.path.join(backup_dir, f"图{idx+1}.png"))
|
||||
img.save(path)
|
||||
path = os.path.abspath(os.path.join(backup_dir, f"图{idx+1}.jpg"))
|
||||
if img.mode != "RGB":
|
||||
img = img.convert("RGB")
|
||||
img.save(path, format="JPEG", quality=95)
|
||||
image_paths.append(path)
|
||||
|
||||
if not image_paths:
|
||||
|
||||
114
sd_service.py
114
sd_service.py
@ -1,13 +1,18 @@
|
||||
"""
|
||||
Stable Diffusion 服务模块
|
||||
封装对 SD WebUI API 的调用,支持 txt2img 和 img2img,支持 ReActor 换脸
|
||||
含图片反 AI 检测后处理管线
|
||||
"""
|
||||
import requests
|
||||
import base64
|
||||
import io
|
||||
import logging
|
||||
import os
|
||||
from PIL import Image
|
||||
import random
|
||||
import math
|
||||
import struct
|
||||
import zlib
|
||||
from PIL import Image, ImageFilter, ImageEnhance
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@ -262,6 +267,109 @@ def get_sd_preset(name: str, model_name: str = None) -> dict:
|
||||
DEFAULT_NEGATIVE = SD_MODEL_PROFILES[DEFAULT_MODEL_PROFILE]["negative_prompt"]
|
||||
|
||||
|
||||
# ==================== 图片反 AI 检测管线 ====================
|
||||
|
||||
def anti_detect_postprocess(img: Image.Image) -> Image.Image:
|
||||
"""对 AI 生成的图片进行后处理,模拟手机拍摄/加工特征,降低 AI 检测率
|
||||
|
||||
处理流程:
|
||||
1. 剥离所有元数据 (EXIF/SD参数/PNG chunks)
|
||||
2. 微小随机裁剪 (模拟手机截图不完美)
|
||||
3. 微小旋转+校正 (破坏像素完美对齐)
|
||||
4. 色彩微扰 (模拟手机屏幕色差)
|
||||
5. 不均匀高斯噪声 (模拟传感器噪声)
|
||||
6. 微小锐化 (模拟手机锐化算法)
|
||||
7. JPEG 压缩回环 (最关键: 引入真实压缩伪影)
|
||||
"""
|
||||
if img.mode != "RGB":
|
||||
img = img.convert("RGB")
|
||||
|
||||
w, h = img.size
|
||||
|
||||
# Step 1: 微小随机裁剪 (1-3 像素, 破坏边界对齐)
|
||||
crop_l = random.randint(0, 3)
|
||||
crop_t = random.randint(0, 3)
|
||||
crop_r = random.randint(0, 3)
|
||||
crop_b = random.randint(0, 3)
|
||||
if crop_l + crop_r < w and crop_t + crop_b < h:
|
||||
img = img.crop((crop_l, crop_t, w - crop_r, h - crop_b))
|
||||
|
||||
# Step 2: 极微旋转 (0.1°-0.5°, 破坏完美像素排列)
|
||||
if random.random() < 0.6:
|
||||
angle = random.uniform(-0.5, 0.5)
|
||||
img = img.rotate(angle, resample=Image.BICUBIC, expand=False,
|
||||
fillcolor=(
|
||||
random.randint(240, 255),
|
||||
random.randint(240, 255),
|
||||
random.randint(240, 255),
|
||||
))
|
||||
|
||||
# Step 3: 色彩微扰 (模拟手机屏幕/相机色差)
|
||||
# 亮度微调
|
||||
brightness_factor = random.uniform(0.97, 1.03)
|
||||
img = ImageEnhance.Brightness(img).enhance(brightness_factor)
|
||||
# 对比度微调
|
||||
contrast_factor = random.uniform(0.97, 1.03)
|
||||
img = ImageEnhance.Contrast(img).enhance(contrast_factor)
|
||||
# 饱和度微调
|
||||
saturation_factor = random.uniform(0.96, 1.04)
|
||||
img = ImageEnhance.Color(img).enhance(saturation_factor)
|
||||
|
||||
# Step 4: 不均匀传感器噪声 (比均匀噪声更像真实相机)
|
||||
try:
|
||||
import numpy as np
|
||||
arr = np.array(img, dtype=np.float32)
|
||||
# 生成不均匀噪声: 中心弱边缘强 (模拟暗角)
|
||||
h_arr, w_arr = arr.shape[:2]
|
||||
y_grid, x_grid = np.mgrid[0:h_arr, 0:w_arr]
|
||||
center_y, center_x = h_arr / 2, w_arr / 2
|
||||
dist = np.sqrt((y_grid - center_y) ** 2 + (x_grid - center_x) ** 2)
|
||||
max_dist = np.sqrt(center_y ** 2 + center_x ** 2)
|
||||
# 噪声强度: 中心 1.0, 边缘 2.5
|
||||
noise_strength = 1.0 + 1.5 * (dist / max_dist)
|
||||
noise_strength = noise_strength[:, :, np.newaxis]
|
||||
# 高斯噪声
|
||||
noise = np.random.normal(0, random.uniform(1.5, 3.0), arr.shape) * noise_strength
|
||||
arr = np.clip(arr + noise, 0, 255).astype(np.uint8)
|
||||
img = Image.fromarray(arr)
|
||||
except ImportError:
|
||||
# numpy 不可用时用 PIL 的简单模糊代替
|
||||
pass
|
||||
|
||||
# Step 5: 轻微锐化 (模拟手机后处理)
|
||||
if random.random() < 0.5:
|
||||
img = img.filter(ImageFilter.SHARPEN)
|
||||
# 再做一次轻微模糊中和, 避免过度锐化
|
||||
img = img.filter(ImageFilter.GaussianBlur(radius=0.3))
|
||||
|
||||
# Step 6: JPEG 压缩回环 (最关键! 引入真实压缩伪影)
|
||||
# 模拟: 手机保存 → 社交平台压缩 → 重新上传
|
||||
quality = random.randint(85, 93) # 质量略低于完美, 像手机存储
|
||||
buf = io.BytesIO()
|
||||
img.save(buf, format="JPEG", quality=quality, subsampling=0)
|
||||
buf.seek(0)
|
||||
img = Image.open(buf).copy() # 重新加载, 已包含 JPEG 伪影
|
||||
|
||||
# Step 7: resize 回原始尺寸附近 (模拟平台缩放)
|
||||
# 微小缩放 ±2%
|
||||
scale = random.uniform(0.98, 1.02)
|
||||
new_w = int(img.width * scale)
|
||||
new_h = int(img.height * scale)
|
||||
if new_w > 100 and new_h > 100:
|
||||
img = img.resize((new_w, new_h), Image.LANCZOS)
|
||||
|
||||
logger.info("🛡️ 图片反检测后处理完成: crop=%dx%d→%dx%d, jpeg_q=%d, scale=%.2f",
|
||||
w, h, img.width, img.height, quality, scale)
|
||||
return img
|
||||
|
||||
|
||||
def strip_metadata(img: Image.Image) -> Image.Image:
|
||||
"""彻底剥离图片所有元数据 (EXIF, SD参数, PNG text chunks)"""
|
||||
clean = Image.new(img.mode, img.size)
|
||||
clean.putdata(list(img.getdata()))
|
||||
return clean
|
||||
|
||||
|
||||
class SDService:
|
||||
"""Stable Diffusion WebUI API 封装"""
|
||||
|
||||
@ -463,6 +571,8 @@ class SDService:
|
||||
images = []
|
||||
for img_b64 in resp.json().get("images", []):
|
||||
img = Image.open(io.BytesIO(base64.b64decode(img_b64)))
|
||||
# 反 AI 检测后处理: 剥离元数据 + 模拟手机拍摄特征
|
||||
img = anti_detect_postprocess(img)
|
||||
images.append(img)
|
||||
return images
|
||||
|
||||
@ -513,6 +623,8 @@ class SDService:
|
||||
images = []
|
||||
for img_b64 in resp.json().get("images", []):
|
||||
img = Image.open(io.BytesIO(base64.b64decode(img_b64)))
|
||||
# 反 AI 检测后处理
|
||||
img = anti_detect_postprocess(img)
|
||||
images.append(img)
|
||||
return images
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user