从任意图片中提取线稿/轮廓图。支持多种方法(Canny边缘检测、素描效果、灰度反色模糊混合、中值滤波+边缘检测),可根据图片类型(照片/插画/动漫/建筑图等)自动推荐最佳方案,也支持批量处理。输出白色背景黑色线稿的高清PNG图片。触发关键词:提取线稿、图片转线稿、线稿提取、图片转轮廓、提取轮廓、照片转线稿、提取线条、图片变线稿、素描提取、轮廓提取、line
Line Art Extractor Skill是一项面向实际任务的技能,主要用于从任意图片中提取线稿/轮廓,输出白底黑线的高清线稿图;手绘临摹参考;
将建筑效果图转为线稿风格;将真人照片转为漫画线稿底图;提取产品/服装轮廓用于设计;提供 4 种线稿提取方法,各有适用场景:;适用图片类型;canny。
它将相关步骤、工具调用和结果整理方式集中到统一流程中,帮助使用者更快完成目标并减少重复操作。从功能定位来看,该技能强调把分散的操作要求整理成清晰、可复用的处理流程,使用户能够围绕既定目标快速准备输入、选择执行方式并获得结构化结果。实际使用前应先确认任务范围、数据来源、运行环境、必要权限和关键参数,再依据技能说明逐步执行;若输入条件不完整,应先补齐信息或采用保守配置,避免因错误假设导致结果偏离需求。执行过程中需要关注工具调用是否成功、接口或依赖是否可用、输出格式是否符合预期,并对异常提示、缺失字段和边界情况进行处理;涉及批量任务时,还应保存进度,避免中断后重复操作。
从任意图片中提取线稿/轮廓,输出白底黑线的高清线稿图。
| 场景 | 说明 |
|---|---|
| 手绘临摹参考 | 将照片/插画转为线稿,方便临摹练习 |
| 二次创作底稿 | 从现有作品提取线稿后重新上色 |
| 建筑草图 | 将建筑效果图转为线稿风格 |
| 漫画分镜 | 将真人照片转为漫画线稿底图 |
| 设计参考 | 提取产品/服装轮廓用于设计 |
提供 4 种线稿提取方法,各有适用场景:
| 方法 | 适用图片类型 | 效果特点 | 速度 |
|---|---|---|---|
canny |
所有类型 | 精准边缘检测,线条干净锐利 | 快 |
sketch |
真人照片 | 模拟铅笔素描效果,有明暗层次 | 快 |
blend |
插画/动漫 | 灰度反色+模糊混合,保留细节丰富 | 快 |
median_edge |
建筑/产品/清晰图片 | 中值去噪后边缘检测,抗噪性强 | 中等 |
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
input_path |
str | 必填 | 输入图片路径(支持 jpg/png/webp/bmp) |
output_path |
str | 自动生成 | 输出路径,默认 {原名}_lineart.png |
method |
str | auto |
提取方法:canny/sketch/blend/median_edge/auto |
threshold1 |
int | 50 | Canny 低阈值(仅 canny/median_edge 方法) |
threshold2 |
int | 150 | Canny 高阈值(仅 canny/median_edge 方法) |
blur_strength |
int | 21 | 模糊核大小,必须为奇数(仅 blend 方法) |
invert |
bool | False | 是否反转为黑底白线(默认白底黑线) |
batch |
bool | False | 是否批量处理(输入目录时自动开启) |
当 method="auto" 时,根据图片特征自动推荐最佳方法:
cannysketchblendmedian_edge如果自动检测不确定,默认使用 blend(适用面最广)。
import cv2
import numpy as np
from PIL import Image, ImageFilter, ImageOps
import os
import glob
def analyze_image(img_path):
"""分析图片特征,自动推荐最佳方法"""
img = cv2.imread(img_path)
if img is None:
raise ValueError(f"无法读取图片: {img_path}")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 计算图像特征
# 1. 对比度(标准差)
contrast = np.std(gray)
# 2. 边缘密度(Canny检测后的非零像素占比)
edges = cv2.Canny(gray, 30, 100)
edge_density = np.count_nonzero(edges) / edges.size
# 3. 色彩饱和度(转换HSV后S通道均值)
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
saturation = np.mean(hsv[:, :, 1])
# 4. 肤色区域占比(HSV空间检测)
skin_mask = cv2.inRange(hsv, np.array([0, 30, 60]), np.array([25, 180, 255]))
skin_ratio = np.count_nonzero(skin_mask) / skin_mask.size
features = {
'contrast': contrast,
'edge_density': edge_density,
'saturation': saturation,
'skin_ratio': skin_ratio
}
# 决策逻辑
if contrast > 60 and edge_density > 0.03:
return 'canny', features
elif skin_ratio > 0.15:
return 'sketch', features
elif saturation > 40 or edge_density > 0.05:
return 'blend', features
else:
return 'median_edge', features
def extract_lineart_canny(img_path, threshold1=50, threshold2=150):
"""Canny 边缘检测法 — 精准锐利"""
img = cv2.imread(img_path)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 高斯模糊降噪
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
# Canny 边缘检测
edges = cv2.Canny(blurred, threshold1, threshold2)
# 白底黑线:反转 Canny 输出
result = cv2.bitwise_not(edges)
return result
def extract_lineart_sketch(img_path):
"""素描效果法 — 铅笔素描感"""
img = cv2.imread(img_path)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 反转灰度图
inverted = 255 - gray
# 高斯模糊反转图
blurred = cv2.GaussianBlur(inverted, (21, 21), 0)
# 颜色减淡混合(模拟铅笔素描)
sketch = cv2.divide(gray, 255 - blurred, scale=256)
# 二值化让线条更清晰
_, result = cv2.threshold(sketch, 200, 255, cv2.THRESH_BINARY)
return result
def extract_lineart_blend(img_path, blur_strength=21):
"""灰度反色模糊混合法 — 保留丰富细节"""
img = cv2.imread(img_path)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 反转灰度图
inverted = 255 - gray
# 对反转图做模糊
blurred = cv2.GaussianBlur(inverted, (blur_strength, blur_strength), 0)
# 与原图灰度做颜色减淡混合
blended = cv2.divide(gray, 255 - blurred, scale=256)
# 增强对比度让线条更突出
result = cv2.convertScaleAbs(blended, alpha=1.5, beta=-30)
# 可选:轻度二值化清理
_, result = cv2.threshold(result, 180, 255, cv2.THRESH_BINARY)
return result
def extract_lineart_median_edge(img_path, threshold1=30, threshold2=80):
"""中值滤波+边缘检测法 — 抗噪性强"""
img = cv2.imread(img_path)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 中值滤波去除噪点
denoised = cv2.medianBlur(gray, 5)
# Canny 边缘检测(用较低阈值保留更多细节)
edges = cv2.Canny(denoised, threshold1, threshold2)
# 膨胀操作让线条更连贯
kernel = np.ones((2, 2), np.uint8)
edges = cv2.dilate(edges, kernel, iterations=1)
# 白底黑线
result = cv2.bitwise_not(edges)
return result
def extract_lineart(img_path, method='auto', **kwargs):
"""
主入口:从图片提取线稿
Args:
img_path: 输入图片路径
method: 提取方法 (canny/sketch/blend/median_edge/auto)
**kwargs: 各方法参数
Returns:
result: 线稿图 (numpy array, 白底黑线)
used_method: 实际使用的方法名
"""
method_map = {
'canny': extract_lineart_canny,
'sketch': extract_lineart_sketch,
'blend': extract_lineart_blend,
'median_edge': extract_lineart_median_edge,
}
if method == 'auto':
method, features = analyze_image(img_path)
if method not in method_map:
raise ValueError(f"未知方法: {method},可选: {list(method_map.keys())}")
func = method_map[method]
# 根据方法传递对应参数
param_map = {
'canny': ['threshold1', 'threshold2'],
'sketch': [],
'blend': ['blur_strength'],
'median_edge': ['threshold1', 'threshold2'],
}
method_kwargs = {k: kwargs[k] for k in param_map[method] if k in kwargs}
result = func(img_path, **method_kwargs)
# 处理反转
if kwargs.get('invert', False):
result = cv2.bitwise_not(result)
return result, method
def save_lineart(result, output_path):
"""保存线稿为PNG"""
os.makedirs(os.path.dirname(output_path) if os.path.dirname(output_path) else '.', exist_ok=True)
cv2.imwrite(output_path, result)
return output_path
def batch_extract(input_dir, output_dir, method='auto', **kwargs):
"""
批量处理目录下的所有图片
Args:
input_dir: 输入目录
output_dir: 输出目录
method: 提取方法
**kwargs: 各方法参数
Returns:
results: 处理结果列表 [{input, output, method, status}]
"""
os.makedirs(output_dir, exist_ok=True)
supported_ext = ('*.jpg', '*.jpeg', '*.png', '*.webp', '*.bmp', '*.tiff')
files = []
for ext in supported_ext:
files.extend(glob.glob(os.path.join(input_dir, ext)))
files.extend(glob.glob(os.path.join(input_dir, ext.upper())))
if not files:
print("未找到支持的图片文件")
return []
results = []
for f in sorted(set(files)):
basename = os.path.splitext(os.path.basename(f))[0]
output_path = os.path.join(output_dir, f"{basename}_lineart.png")
try:
result, used_method = extract_lineart(f, method=method, **kwargs)
save_lineart(result, output_path)
results.append({
'input': f,
'output': output_path,
'method': used_method,
'status': 'success'
})
print(f" [OK] {basename} → {used_method}")
except Exception as e:
results.append({
'input': f,
'output': output_path,
'method': method,
'status': f'error: {str(e)}'
})
print(f" [FAIL] {basename}: {e}")
return results
from line_art_extractor import extract_lineart, save_lineart
# 自动选择最佳方法
result, method = extract_lineart("photo.jpg", method='auto')
output_path = save_lineart(result, "photo_lineart.png")
print(f"使用方法: {method},输出: {output_path}")
# 真人照片 → 素描效果
result, _ = extract_lineart("portrait.jpg", method='sketch')
save_lineart(result, "portrait_sketch.png")
# 插画 → 灰度混合法
result, _ = extract_lineart("illustration.png", method='blend', blur_strength=25)
save_lineart(result, "illustration_lineart.png")
# 建筑效果图 → Canny
result, _ = extract_lineart("architecture.jpg", method='canny', threshold1=40, threshold2=120)
save_lineart(result, "architecture_lineart.png")
from line_art_extractor import batch_extract
results = batch_extract(
input_dir="./images",
output_dir="./output_lineart",
method='auto'
)
print(f"处理完成: {len([r for r in results if r['status']=='success'])}/{len(results)}")
result, _ = extract_lineart("photo.jpg", method='blend', invert=True)
save_lineart(result, "photo_lineart_inverted.png")
| 图片类型 | 推荐方法 | 推荐参数 |
|---|---|---|
| 真人照片 | sketch |
默认参数 |
| 动漫插画 | blend |
blur_strength=17(线条更细) |
| 水彩/油画 | blend |
blur_strength=25(保留柔和感) |
| 建筑/产品 | canny |
threshold1=40, threshold2=120 |
| 线条复杂的手绘 | median_edge |
threshold1=20, threshold2=60 |
| 高对比度图像 | canny |
threshold1=80, threshold2=200 |
invert=True 反转canny 方法效果最佳| 库 | 版本要求 | 用途 |
|---|---|---|
| opencv-python | >= 4.0 | 图像处理核心 |
| numpy | >= 1.20 | 数值计算 |
| Pillow | >= 9.0 | 图像格式支持 |
所有依赖均已预装,无需额外安装。
相关专题
热门下载
相关下载
精品课程
共6课时 | 54.7万人学习
共89课时 | 134.1万人学习
共49课时 | 82.6万人学习
最新文章