sketch.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738
  1. """
  2. 线稿图生成模块
  3. 功能:
  4. 1. 从服装款式图生成平铺图
  5. 2. 使用 ControlNet linear 模型从平铺图提取线稿
  6. 3. 自动质量检查
  7. 4. 批量处理图片
  8. """
  9. import os
  10. import json
  11. import glob
  12. import io
  13. import tempfile
  14. import requests
  15. from datetime import datetime
  16. from typing import List, Optional
  17. from PIL import Image
  18. from .check import process_image_pair_with_gemini
  19. from .conf import check_prompt
  20. from .upload_tos import process_cropped_upload, upload_image
  21. from .qwen_edit import qwen_edit
  22. from .llm import llm_request
  23. from .conf import ali_ky
  24. from .prompt import flat_layout_prompt_v2
  25. from .logger_setup import logger
  26. # ==================== 常量定义 ====================
  27. DEFAULT_SKETCH_DIR = r"D:\线稿图\线稿图"
  28. DEFAULT_LOG_FILE = "sketch_log.json"
  29. # 默认线稿图生成提示词
  30. DEFAULT_SKETCH_PROMPT = (
  31. "生成图片里衣服(如果有内衬则包括内衬)的服装平面款式图,"
  32. "要平铺效果的线稿,仅仅保留外部轮廓和关键结构,"
  33. "无多余拼接线,去除颜色,保持原比例,去除褶皱,"
  34. "不要添加或删减元素,保持衣服的细节,只保留衣服的线稿,"
  35. "去除其他非衣服之外的元素"
  36. )
  37. IMAGE_EXTENSIONS = ['.jpg', '.jpeg', '.png', '.bmp', '.gif',
  38. '.JPG', '.JPEG', '.PNG', '.BMP', '.GIF']
  39. # 最大重试次数
  40. MAX_RETRY_COUNT = 2
  41. # ==================== 平铺图生成函数 ====================
  42. def generate_flat_layout_from_url(
  43. image_url: str,
  44. prompt: Optional[str] = None
  45. ) -> Optional[str]:
  46. """
  47. 从图片URL生成平铺图
  48. Args:
  49. image_url: 款式图片URL
  50. prompt: 平铺图生成提示词,如果为None则自动生成
  51. Returns:
  52. 生成的平铺图URL,失败返回None
  53. """
  54. try:
  55. # 下载图片到临时文件
  56. response = requests.get(image_url, timeout=30)
  57. response.raise_for_status()
  58. # 创建临时文件
  59. with tempfile.NamedTemporaryFile(suffix='.jpg', delete=False) as tmp_file:
  60. tmp_file.write(response.content)
  61. tmp_path = tmp_file.name
  62. try:
  63. # 如果没有提供提示词,使用LLM生成
  64. if prompt is None:
  65. logger.info("自动生成平铺图提示词...")
  66. llm = llm_request(api_key=ali_ky[0], base_url=ali_ky[1], model="qwen3-vl-plus")
  67. prompt = llm.llm_mm_request(
  68. usr_text="帮我生成这条衣服的平铺图指令",
  69. img=tmp_path,
  70. sys_text=flat_layout_prompt_v2
  71. )
  72. logger.info(f"生成的提示词: {prompt}")
  73. # 创建临时文件保存平铺图
  74. with tempfile.NamedTemporaryFile(suffix='.png', delete=False) as flat_tmp:
  75. flat_layout_path = flat_tmp.name
  76. # 使用qwen_edit生成平铺图
  77. logger.info("开始生成平铺图...")
  78. qwen_edit(tmp_path, prompt, flat_layout_path)
  79. # 检查文件是否生成成功
  80. if not os.path.exists(flat_layout_path) or os.path.getsize(flat_layout_path) == 0:
  81. logger.error("平铺图生成失败")
  82. return None
  83. # 上传平铺图到TOS
  84. flat_layout_url = upload_image(flat_layout_path)
  85. # 清理临时文件
  86. try:
  87. os.unlink(tmp_path)
  88. os.unlink(flat_layout_path)
  89. except:
  90. pass
  91. if flat_layout_url:
  92. logger.info(f"✅ 平铺图生成成功: {flat_layout_url}")
  93. return flat_layout_url
  94. else:
  95. logger.error("上传平铺图失败")
  96. return None
  97. except Exception as e:
  98. logger.error(f"生成平铺图时出错: {e}")
  99. # 清理临时文件
  100. try:
  101. os.unlink(tmp_path)
  102. except:
  103. pass
  104. return None
  105. except Exception as e:
  106. logger.error(f"下载图片失败: {e}")
  107. return None
  108. # ==================== ControlNet Linear 线稿提取函数 ====================
  109. def extract_lineart_with_controlnet(
  110. image_url: str,
  111. controlnet_model: str = "lineart",
  112. api_key: Optional[str] = None
  113. ) -> Optional[Image.Image]:
  114. """
  115. 使用 ControlNet linear 模型从图片中提取线稿
  116. Args:
  117. image_url: 输入图片URL(平铺图)
  118. controlnet_model: ControlNet模型类型,默认为 "lineart"
  119. api_key: API密钥(可选,从环境变量获取)
  120. Returns:
  121. PIL Image对象(线稿图),失败返回None
  122. """
  123. try:
  124. # 尝试使用 FAL API(如果可用)
  125. fal_key = api_key or os.environ.get("FAL_KEY")
  126. if fal_key:
  127. return _extract_lineart_with_fal(image_url, fal_key, controlnet_model)
  128. else:
  129. # 如果没有 FAL API,使用其他 ControlNet 服务
  130. # 这里可以添加其他 ControlNet API 调用
  131. logger.warning("FAL_KEY 未设置,尝试使用本地 ControlNet 处理")
  132. return _extract_lineart_local(image_url)
  133. except Exception as e:
  134. logger.error(f"提取线稿时出错: {e}")
  135. return None
  136. def _extract_lineart_with_fal(
  137. image_url: str,
  138. api_key: str,
  139. controlnet_model: str = "lineart"
  140. ) -> Optional[Image.Image]:
  141. """
  142. 使用 FAL API 的 ControlNet 服务提取线稿
  143. Args:
  144. image_url: 输入图片URL
  145. api_key: FAL API密钥
  146. controlnet_model: ControlNet模型类型
  147. Returns:
  148. PIL Image对象,失败返回None
  149. """
  150. try:
  151. import fal_client
  152. logger.info(f"使用 FAL API 提取线稿: {image_url}")
  153. # 调用 FAL API 的 ControlNet lineart 模型
  154. result = fal_client.subscribe(
  155. "fal-ai/controlnet-lineart",
  156. arguments={
  157. "image_url": image_url,
  158. "model": controlnet_model
  159. },
  160. api_key=api_key
  161. )
  162. # 获取结果图片URL
  163. if result and "images" in result:
  164. output_url = result["images"][0].get("url") if isinstance(result["images"], list) else result["images"].get("url")
  165. if output_url:
  166. # 下载生成的线稿图
  167. response = requests.get(output_url, timeout=30)
  168. response.raise_for_status()
  169. img = Image.open(io.BytesIO(response.content)).convert("RGB")
  170. logger.info("✅ 线稿提取成功")
  171. return img
  172. logger.error("FAL API 返回结果格式错误")
  173. return None
  174. except ImportError:
  175. logger.warning("fal_client 未安装,尝试使用 HTTP 请求")
  176. return _extract_lineart_with_http(image_url, api_key, controlnet_model)
  177. except Exception as e:
  178. logger.error(f"FAL API 调用失败: {e}")
  179. return None
  180. def _extract_lineart_with_http(
  181. image_url: str,
  182. api_key: str,
  183. controlnet_model: str = "lineart"
  184. ) -> Optional[Image.Image]:
  185. """
  186. 使用 HTTP 请求调用 FAL API 提取线稿
  187. Args:
  188. image_url: 输入图片URL
  189. api_key: FAL API密钥
  190. controlnet_model: ControlNet模型类型
  191. Returns:
  192. PIL Image对象,失败返回None
  193. """
  194. try:
  195. url = "https://fal.run/fal-ai/controlnet-lineart"
  196. headers = {
  197. "Authorization": f"Key {api_key}",
  198. "Content-Type": "application/json"
  199. }
  200. payload = {
  201. "image_url": image_url,
  202. "model": controlnet_model
  203. }
  204. response = requests.post(url, json=payload, headers=headers, timeout=60)
  205. response.raise_for_status()
  206. result = response.json()
  207. # 获取结果图片URL
  208. if result and "images" in result:
  209. output_url = result["images"][0].get("url") if isinstance(result["images"], list) else result["images"].get("url")
  210. if output_url:
  211. # 下载生成的线稿图
  212. img_response = requests.get(output_url, timeout=30)
  213. img_response.raise_for_status()
  214. img = Image.open(io.BytesIO(img_response.content)).convert("RGB")
  215. logger.info("✅ 线稿提取成功")
  216. return img
  217. logger.error("HTTP API 返回结果格式错误")
  218. return None
  219. except Exception as e:
  220. logger.error(f"HTTP API 调用失败: {e}")
  221. return None
  222. def _extract_lineart_local(image_url: str) -> Optional[Image.Image]:
  223. """
  224. 使用本地方法提取线稿(简单的边缘检测)
  225. 注意:这是一个备用方案,效果不如 ControlNet
  226. 如果可能,建议使用 FAL API 或其他 ControlNet 服务
  227. Args:
  228. image_url: 输入图片URL
  229. Returns:
  230. PIL Image对象,失败返回None
  231. """
  232. try:
  233. import cv2
  234. import numpy as np
  235. logger.info("使用本地方法提取线稿(边缘检测)...")
  236. # 下载图片
  237. response = requests.get(image_url, timeout=30)
  238. response.raise_for_status()
  239. # 将字节数据转换为numpy数组
  240. img_array = np.asarray(bytearray(response.content), dtype=np.uint8)
  241. img = cv2.imdecode(img_array, cv2.IMREAD_COLOR)
  242. # 转换为灰度图
  243. gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
  244. # 使用 Canny 边缘检测
  245. edges = cv2.Canny(gray, 50, 150)
  246. # 反转颜色(黑底白线 -> 白底黑线)
  247. edges = 255 - edges
  248. # 转换回 PIL Image
  249. pil_img = Image.fromarray(edges).convert("RGB")
  250. logger.info("✅ 本地线稿提取完成(使用边缘检测)")
  251. return pil_img
  252. except ImportError:
  253. logger.error("OpenCV 未安装,无法使用本地方法")
  254. return None
  255. except Exception as e:
  256. logger.error(f"本地线稿提取失败: {e}")
  257. return None
  258. # ==================== 核心功能函数 ====================
  259. def generate_sketch(
  260. image_url: str,
  261. prompt: Optional[str] = None,
  262. save_dir: Optional[str] = None,
  263. max_retries: int = MAX_RETRY_COUNT,
  264. auto_check: bool = True,
  265. flat_layout_prompt: Optional[str] = None,
  266. controlnet_model: str = "lineart"
  267. ) -> Optional[str]:
  268. """
  269. 生成服装线稿图(新流程:先生成平铺图,再提取线稿)
  270. 流程:
  271. 1. 从款式图生成平铺图(使用 qwen_edit)
  272. 2. 使用 ControlNet linear 模型从平铺图提取线稿
  273. 3. 质量检查(可选)
  274. Args:
  275. image_url: 款式图片URL
  276. prompt: 线稿图提示词(已废弃,保留用于兼容)
  277. save_dir: 保存目录,如果为None则不保存到本地
  278. max_retries: 最大重试次数
  279. auto_check: 是否自动质量检查
  280. flat_layout_prompt: 平铺图生成提示词,如果为None则自动生成
  281. controlnet_model: ControlNet模型类型,默认为 "lineart"
  282. Returns:
  283. 生成的线稿图URL,失败返回None
  284. """
  285. if not image_url:
  286. logger.error("图片URL不能为空")
  287. return None
  288. logger.info(f"开始生成线稿图(新流程): {image_url}")
  289. logger.info("流程:款式图 → 平铺图 → 线稿图")
  290. # 尝试生成线稿图,最多重试max_retries次
  291. for attempt in range(max_retries):
  292. logger.info(f"第 {attempt + 1}/{max_retries} 次尝试生成线稿图")
  293. try:
  294. # 第一步:生成平铺图
  295. logger.info("=" * 50)
  296. logger.info("步骤 1/2: 生成平铺图")
  297. logger.info("=" * 50)
  298. flat_layout_url = generate_flat_layout_from_url(
  299. image_url=image_url,
  300. prompt=flat_layout_prompt
  301. )
  302. if flat_layout_url is None:
  303. logger.warning(f"第 {attempt + 1} 次生成平铺图失败,继续重试")
  304. continue
  305. logger.info(f"✅ 平铺图生成成功: {flat_layout_url}")
  306. # 第二步:使用 ControlNet linear 提取线稿
  307. logger.info("=" * 50)
  308. logger.info("步骤 2/2: 使用 ControlNet linear 提取线稿")
  309. logger.info("=" * 50)
  310. sketch_image = extract_lineart_with_controlnet(
  311. image_url=flat_layout_url,
  312. controlnet_model=controlnet_model
  313. )
  314. if sketch_image is None:
  315. logger.warning(f"第 {attempt + 1} 次提取线稿失败,继续重试")
  316. continue
  317. # 上传线稿图获取URL
  318. sketch_url = process_cropped_upload(sketch_image)
  319. if sketch_url is None:
  320. logger.warning(f"第 {attempt + 1} 次上传线稿图失败,继续重试")
  321. continue
  322. logger.info(f"✅ 线稿图提取成功: {sketch_url}")
  323. # 如果启用自动检查,进行质量验证
  324. if auto_check:
  325. logger.info("=" * 50)
  326. logger.info("进行质量检查...")
  327. logger.info("=" * 50)
  328. check_result = process_image_pair_with_gemini(
  329. image1_url=image_url,
  330. image2_url=sketch_url,
  331. prompt=check_prompt
  332. )
  333. if check_result:
  334. check_result = check_result.strip()
  335. # 检查是否通过(回答"是")
  336. if check_result and ("是" in check_result or check_result[0] == "是"):
  337. logger.info(f"✅ 质量检查通过: {check_result}")
  338. logger.info(f"✅ 线稿图生成成功: {sketch_url}")
  339. return sketch_url
  340. else:
  341. logger.warning(f"⚠️ 质量检查未通过: {check_result}")
  342. if attempt < max_retries - 1:
  343. logger.info("继续重试...")
  344. continue
  345. else:
  346. logger.warning("质量检查失败,但继续使用生成的图片")
  347. # 如果没有启用检查或检查失败但达到最大重试次数,返回结果
  348. logger.info(f"✅ 线稿图生成成功: {sketch_url}")
  349. return sketch_url
  350. except Exception as e:
  351. logger.error(f"第 {attempt + 1} 次尝试时出错: {e}")
  352. if attempt < max_retries - 1:
  353. continue
  354. else:
  355. logger.error(f"❌ 生成线稿图失败: {e}")
  356. return None
  357. logger.error("❌ 达到最大重试次数,生成线稿图失败")
  358. return None
  359. def generate_sketch_from_local(
  360. image_path: str,
  361. prompt: Optional[str] = None,
  362. save_dir: Optional[str] = None,
  363. max_retries: int = MAX_RETRY_COUNT,
  364. auto_check: bool = True,
  365. model: str = "gemini-2.5-flash-image",
  366. resolution: str = "1K"
  367. ) -> Optional[str]:
  368. """
  369. 从本地图片文件生成线稿图
  370. 首先上传本地图片到TOS获取URL,然后调用generate_sketch
  371. Args:
  372. image_path: 本地图片路径
  373. prompt: 提示词
  374. save_dir: 保存目录
  375. max_retries: 最大重试次数
  376. auto_check: 是否自动质量检查
  377. model: 使用的模型
  378. resolution: 分辨率
  379. Returns:
  380. 生成的线稿图URL,失败返回None
  381. """
  382. if not os.path.exists(image_path):
  383. logger.error(f"图片文件不存在: {image_path}")
  384. return None
  385. try:
  386. # 上传本地图片获取URL
  387. from .upload_tos import upload_image
  388. image_url = upload_image(image_path)
  389. if image_url is None:
  390. logger.error("上传图片失败")
  391. return None
  392. logger.info(f"图片已上传: {image_url}")
  393. # 调用生成函数
  394. return generate_sketch(
  395. image_url=image_url,
  396. prompt=prompt,
  397. save_dir=save_dir,
  398. max_retries=max_retries,
  399. auto_check=auto_check,
  400. model=model,
  401. resolution=resolution
  402. )
  403. except Exception as e:
  404. logger.error(f"处理本地图片时出错: {e}")
  405. return None
  406. # ==================== 工具函数 ====================
  407. def get_image_files(directory: str, extensions: Optional[List[str]] = None) -> List[str]:
  408. """
  409. 获取目录下所有图片文件
  410. Args:
  411. directory: 目录路径
  412. extensions: 图片扩展名列表
  413. Returns:
  414. 图片文件路径列表(已排序)
  415. """
  416. if extensions is None:
  417. extensions = IMAGE_EXTENSIONS
  418. image_files = []
  419. if not os.path.exists(directory):
  420. return image_files
  421. for ext in extensions:
  422. pattern = os.path.join(directory, f'*{ext}')
  423. image_files.extend(glob.glob(pattern))
  424. return sorted(image_files)
  425. def save_sketch_log(image_url: str, sketch_url: str, prompt: str,
  426. log_file: str = DEFAULT_LOG_FILE) -> None:
  427. """
  428. 保存线稿图生成记录到JSON文件
  429. Args:
  430. image_url: 原始图片URL
  431. sketch_url: 生成的线稿图URL
  432. prompt: 使用的提示词
  433. log_file: 日志文件路径
  434. """
  435. log_data = {
  436. "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
  437. "image_url": image_url,
  438. "sketch_url": sketch_url,
  439. "prompt": prompt
  440. }
  441. # 读取现有数据
  442. if os.path.exists(log_file):
  443. try:
  444. with open(log_file, 'r', encoding='utf-8') as f:
  445. data = json.load(f)
  446. except (json.JSONDecodeError, FileNotFoundError):
  447. data = []
  448. else:
  449. data = []
  450. # 添加新记录
  451. data.append(log_data)
  452. # 保存到文件
  453. with open(log_file, 'w', encoding='utf-8') as f:
  454. json.dump(data, f, ensure_ascii=False, indent=2)
  455. logger.info(f"✅ 已保存记录到 {log_file}")
  456. # ==================== 批量处理功能 ====================
  457. def batch_generate_sketch_from_urls(
  458. image_urls: List[str],
  459. prompt: Optional[str] = None,
  460. max_retries: int = MAX_RETRY_COUNT,
  461. auto_check: bool = True,
  462. log_file: Optional[str] = None
  463. ) -> dict:
  464. """
  465. 批量从图片URL列表生成线稿图
  466. Args:
  467. image_urls: 图片URL列表
  468. prompt: 提示词
  469. max_retries: 最大重试次数
  470. auto_check: 是否自动质量检查
  471. log_file: 日志文件路径,如果为None则不保存
  472. Returns:
  473. 处理结果字典,包含成功和失败的数量
  474. """
  475. logger.info(f"开始批量处理 {len(image_urls)} 个图片")
  476. success_count = 0
  477. fail_count = 0
  478. all_results = []
  479. for idx, image_url in enumerate(image_urls, 1):
  480. logger.info(f"\n{'='*50}")
  481. logger.info(f"处理第 {idx}/{len(image_urls)} 个图片")
  482. logger.info(f"{'='*50}")
  483. try:
  484. sketch_url = generate_sketch(
  485. image_url=image_url,
  486. prompt=prompt,
  487. max_retries=max_retries,
  488. auto_check=auto_check
  489. )
  490. if sketch_url:
  491. result = {
  492. "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
  493. "image_url": image_url,
  494. "sketch_url": sketch_url,
  495. "prompt": prompt or DEFAULT_SKETCH_PROMPT,
  496. "success": True
  497. }
  498. all_results.append(result)
  499. success_count += 1
  500. # 保存日志
  501. if log_file:
  502. save_sketch_log(image_url, sketch_url, prompt or DEFAULT_SKETCH_PROMPT, log_file)
  503. else:
  504. result = {
  505. "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
  506. "image_url": image_url,
  507. "success": False,
  508. "error": "生成失败"
  509. }
  510. all_results.append(result)
  511. fail_count += 1
  512. except Exception as e:
  513. logger.error(f"处理图片时出错: {e}")
  514. result = {
  515. "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
  516. "image_url": image_url,
  517. "success": False,
  518. "error": str(e)
  519. }
  520. all_results.append(result)
  521. fail_count += 1
  522. continue
  523. # 保存结果到文件
  524. if log_file:
  525. try:
  526. with open(log_file, 'w', encoding='utf-8') as f:
  527. json.dump(all_results, f, ensure_ascii=False, indent=2)
  528. logger.info(f"✅ 已保存处理结果到: {log_file}")
  529. except Exception as e:
  530. logger.error(f"保存文件时出错: {e}")
  531. logger.info(f"\n{'='*50}")
  532. logger.info(f"✅ 批量处理完成!")
  533. logger.info(f" 成功: {success_count} 个")
  534. logger.info(f" 失败: {fail_count} 个")
  535. logger.info(f"{'='*50}")
  536. return {
  537. "success_count": success_count,
  538. "fail_count": fail_count,
  539. "total": len(image_urls),
  540. "results": all_results
  541. }
  542. def batch_generate_sketch_from_directory(
  543. directory: str,
  544. prompt: Optional[str] = None,
  545. max_retries: int = MAX_RETRY_COUNT,
  546. auto_check: bool = True,
  547. log_file: Optional[str] = None
  548. ) -> dict:
  549. """
  550. 从目录中扫描图片,批量生成线稿图
  551. Args:
  552. directory: 图片目录路径
  553. prompt: 提示词
  554. max_retries: 最大重试次数
  555. auto_check: 是否自动质量检查
  556. log_file: 日志文件路径
  557. Returns:
  558. 处理结果字典
  559. """
  560. # 获取所有图片文件
  561. image_files = get_image_files(directory)
  562. if not image_files:
  563. logger.warning(f"在目录 {directory} 中未找到图片文件")
  564. return {
  565. "success_count": 0,
  566. "fail_count": 0,
  567. "total": 0,
  568. "results": []
  569. }
  570. logger.info(f"找到 {len(image_files)} 个图片文件")
  571. # 上传所有图片获取URL
  572. from .upload_tos import upload_image
  573. image_urls = []
  574. for image_path in image_files:
  575. try:
  576. image_url = upload_image(image_path)
  577. if image_url:
  578. image_urls.append(image_url)
  579. except Exception as e:
  580. logger.error(f"上传图片失败 {image_path}: {e}")
  581. continue
  582. logger.info(f"成功上传 {len(image_urls)} 个图片")
  583. # 批量生成线稿图
  584. return batch_generate_sketch_from_urls(
  585. image_urls=image_urls,
  586. prompt=prompt,
  587. max_retries=max_retries,
  588. auto_check=auto_check,
  589. log_file=log_file
  590. )
  591. # ==================== 主程序入口 ====================
  592. if __name__ == "__main__":
  593. # 示例:从单个图片URL生成线稿图
  594. test_image_url = "https://example.com/garment.jpg"
  595. result = generate_sketch(test_image_url)
  596. if result:
  597. print(f"✅ 线稿图生成成功: {result}")
  598. else:
  599. print("❌ 线稿图生成失败")