【教学类-97-11】20251127虚拟人物照片转(Python+PS)简笔画效果

  • 时间:2025-12-02 22:00 作者: 来源: 阅读:4
  • 扫一扫,手机访问
摘要: 背景需求 用通义万相把虚拟人物照片转成简笔画线条 【教学类-97-10】20251126虚拟人物照片转简笔画效果https://mp.csdn.net/mp_blog/creation/editor/155297482 我想试试用Python模拟PS的功能将人物照片批量转为简笔画线条效果。 一、用黑白化方式提取图像 ''' 对“切边原图”操作,制

背景需求

用通义万相把虚拟人物照片转成简笔画线条

【教学类-97-10】20251126虚拟人物照片转简笔画效果https://mp.csdn.net/mp_blog/creation/editor/155297482

我想试试用Python模拟PS的功能将人物照片批量转为简笔画线条效果。

一、用黑白化方式提取图像



'''
对“切边原图”操作,制作白色斑点狗——黑白化+背景透明+切边+统一大小
豆包,阿夏
20251117
'''
 
 
import os
from PIL import Image
import numpy as np
 
def convert_to_pure_bw(image_path, output_path, threshold=128):
    """
    将图片转为纯黑白两色(黑色0,0,0和白色255,255,255)
    
    参数:
    image_path: 输入图片路径
    output_path: 输出图片路径
    threshold: 二值化阈值
    """
    try:
        img = Image.open(image_path)
        img = img.convert('L')  # 转为灰度图
        
        # 二值化处理
        binary_img = img.point(lambda x: 0 if x < threshold else 255, '1')
        binary_img = binary_img.convert('RGB')  # 转回RGB模式
        
        # 保存黑白图片
        binary_img.save(output_path)
        print(f"黑白图生成: {os.path.basename(image_path)}")
        return output_path
        
    except Exception as e:
        print(f"处理图片 {image_path} 时出错: {str(e)}")
        return None
 
def crop_transparent_edges(image, margin=0):
    """
    裁剪掉图片的透明边缘,并保留指定的间距
    
    参数:
    image: PIL Image对象(RGBA模式)
    margin: 要保留的间距(磅/像素)
    
    返回:
    裁剪后的PIL Image对象
    """
    # 转换为numpy数组
    data = np.array(image)
    
    # 获取alpha通道
    alpha = data[:, :, 3]
    
    # 找到非透明像素的位置
    non_transparent = np.where(alpha > 0)
    
    if len(non_transparent[0]) == 0:
        # 如果全是透明像素,返回原图
        return image
    
    # 获取非透明区域的边界
    top = np.min(non_transparent[0])
    bottom = np.max(non_transparent[0])
    left = np.min(non_transparent[1])
    right = np.max(non_transparent[1])
    
    # 添加间距
    top = max(0, top - margin)
    bottom = min(image.height - 1, bottom + margin)
    left = max(0, left - margin)
    right = min(image.width - 1, right + margin)
    
    # 裁剪图片
    cropped_image = image.crop((left, top, right + 1, bottom + 1))
    
    return cropped_image
 
def make_background_transparent(bw_image_path, output_path, tolerance=30, margin=0):
    """
    将黑白图片的白色背景变为透明并裁剪透明边缘
    
    参数:
    bw_image_path: 黑白图片路径
    output_path: 输出图片路径
    tolerance: 颜色容差,控制背景识别的灵敏度
    margin: 裁剪后保留的间距(磅/像素)
    """
    # 打开黑白图片并转换为RGBA模式
    with Image.open(bw_image_path) as img:
        # 转换为RGBA模式
        img = img.convert('RGBA')
        
        # 获取图片数据
        data = np.array(img)
        red, green, blue, alpha = data.T
        
        # 创建白色背景掩码:判断像素是否为白色(在容差范围内)
        white_mask = (
            (red >= 255 - tolerance) & (red <= 255) &
            (green >= 255 - tolerance) & (green <= 255) &
            (blue >= 255 - tolerance) & (blue <= 255)
        )
        
        # 将白色背景像素的alpha通道设为0(透明)
        data[white_mask.T] = (255, 255, 255, 0)
        
        # 转换回Image
        result = Image.fromarray(data)
        
        # 裁剪透明边缘并保留间距
        cropped_result = crop_transparent_edges(result, margin)
        
        # 保存结果
        cropped_result.save(output_path, 'PNG')
 
def resize_to_uniform_size(image_path, output_path, target_size=(200, 200)):
    """
    将图片调整为统一大小,保持宽高比,在空白处填充透明
    
    参数:
    image_path: 输入图片路径
    output_path: 输出图片路径
    target_size: 目标尺寸 (宽, 高)
    """
    with Image.open(image_path) as img:
        if img.mode != 'RGBA':
            img = img.convert('RGBA')
        
        # 创建新的透明背景图片
        new_img = Image.new('RGBA', target_size, (255, 255, 255, 0))
        
        # 计算缩放比例,保持宽高比
        img_ratio = img.width / img.height
        target_ratio = target_size[0] / target_size[1]
        
        if img_ratio > target_ratio:
            # 图片较宽,按宽度缩放
            new_width = target_size[0]
            new_height = int(target_size[0] / img_ratio)
        else:
            # 图片较高,按高度缩放
            new_height = target_size[1]
            new_width = int(target_size[1] * img_ratio)
        
        # 缩放图片
        resized_img = img.resize((new_width, new_height), Image.Resampling.LANCZOS)
        
        # 计算居中位置
        x = (target_size[0] - new_width) // 2
        y = (target_size[1] - new_height) // 2
        
        # 将缩放后的图片粘贴到新图片上
        new_img.paste(resized_img, (x, y), resized_img)
        
        # 保存结果
        new_img.save(output_path, 'PNG')
 
def resize_to_exact_size(image_path, output_path, target_size=(200, 200)):
    """
    将图片拉伸到精确的目标尺寸
    
    参数:
    image_path: 输入图片路径
    output_path: 输出图片路径
    target_size: 目标尺寸 (宽, 高)
    """
    with Image.open(image_path) as img:
        if img.mode != 'RGBA':
            img = img.convert('RGBA')
        
        # 直接拉伸到目标尺寸
        resized_img = img.resize(target_size, Image.Resampling.LANCZOS)
        
        # 保存结果
        resized_img.save(output_path, 'PNG')
 
def process_single_image_keep_ratio(image_path, output_path, bw_threshold=128, transparency_tolerance=30, margin=0, target_size=(200, 200)):
    """
    处理单张图片:黑白化 → 透明化 → 保持比例统一尺寸
    
    参数:
    image_path: 输入图片路径
    output_path: 输出图片路径
    bw_threshold: 黑白化阈值
    transparency_tolerance: 透明化容差
    margin: 裁剪边距
    target_size: 目标尺寸
    """
    try:
        # 步骤1: 转为黑白图片(临时文件)
        temp_bw_path = output_path.replace('.png', '_bw_temp.png')
        bw_path = convert_to_pure_bw(image_path, temp_bw_path, bw_threshold)
        
        if not bw_path:
            return False
        
        # 步骤2: 背景透明化(临时文件)
        temp_transparent_path = output_path.replace('.png', '_trans_temp.png')
        make_background_transparent(bw_path, temp_transparent_path, transparency_tolerance, margin)
        
        # 步骤3: 保持比例统一尺寸
        resize_to_uniform_size(temp_transparent_path, output_path, target_size)
        
        # 清理临时文件
        if os.path.exists(temp_bw_path):
            os.remove(temp_bw_path)
        if os.path.exists(temp_transparent_path):
            os.remove(temp_transparent_path)
        
        return True
        
    except Exception as e:
        print(f"处理图片 {image_path} 时出错: {str(e)}")
        # 清理临时文件
        for temp_path in [temp_bw_path, temp_transparent_path]:
            if 'temp_path' in locals() and os.path.exists(temp_path):
                os.remove(temp_path)
        return False
 
def process_single_image_stretch(image_path, output_path, bw_threshold=128, transparency_tolerance=30, margin=0, target_size=(200, 200)):
    """
    处理单张图片:黑白化 → 透明化 → 精确拉伸到目标尺寸
    
    参数:
    image_path: 输入图片路径
    output_path: 输出图片路径
    bw_threshold: 黑白化阈值
    transparency_tolerance: 透明化容差
    margin: 裁剪边距
    target_size: 目标尺寸
    """
    try:
        # 步骤1: 转为黑白图片(临时文件)
        temp_bw_path = output_path.replace('.png', '_bw_temp.png')
        bw_path = convert_to_pure_bw(image_path, temp_bw_path, bw_threshold)
        
        if not bw_path:
            return False
        
        # 步骤2: 背景透明化(临时文件)
        temp_transparent_path = output_path.replace('.png', '_trans_temp.png')
        make_background_transparent(bw_path, temp_transparent_path, transparency_tolerance, margin)
        
        # 步骤3: 精确拉伸到目标尺寸
        resize_to_exact_size(temp_transparent_path, output_path, target_size)
        
        # 清理临时文件
        if os.path.exists(temp_bw_path):
            os.remove(temp_bw_path)
        if os.path.exists(temp_transparent_path):
            os.remove(temp_transparent_path)
        
        return True
        
    except Exception as e:
        print(f"处理图片 {image_path} 时出错: {str(e)}")
        # 清理临时文件
        for temp_path in [temp_bw_path, temp_transparent_path]:
            if 'temp_path' in locals() and os.path.exists(temp_path):
                os.remove(temp_path)
        return False
 
def batch_process_images(input_dir, output_dir_keep_ratio, output_dir_stretch, 
                        bw_threshold=128, transparency_tolerance=30, margin=0, target_size=(200, 200)):
    """
    批量处理文件夹中的所有图片,生成两个版本的输出
    
    参数:
    input_dir: 输入文件夹路径
    output_dir_keep_ratio: 保持比例的输出文件夹路径
    output_dir_stretch: 拉伸撑满的输出文件夹路径
    bw_threshold: 黑白化阈值
    transparency_tolerance: 透明化容差
    margin: 裁剪边距
    target_size: 目标尺寸
    """
    # 创建输出文件夹(如果不存在)
    os.makedirs(output_dir_keep_ratio, exist_ok=True)
    os.makedirs(output_dir_stretch, exist_ok=True)
    
    # 支持的图片格式
    supported_formats = ('.png', '.jpg', '.jpeg', '.gif', '.bmp', '.tiff')
    
    # 遍历输入文件夹中的所有文件
    processed_count = 0
    for filename in os.listdir(input_dir):
        # 检查文件是否为支持的图片格式
        if filename.lower().endswith(supported_formats):
            input_path = os.path.join(input_dir, filename)
            
            # 构建输出文件路径,统一保存为PNG格式
            output_filename = os.path.splitext(filename)[0] + '.png'
            
            # 保持比例版本
            output_path_keep_ratio = os.path.join(output_dir_keep_ratio, output_filename)
            # 拉伸撑满版本
            output_path_stretch = os.path.join(output_dir_stretch, output_filename)
            
            # 处理保持比例版本
            success_keep_ratio = process_single_image_keep_ratio(
                input_path, output_path_keep_ratio, 
                bw_threshold, transparency_tolerance, 
                margin, target_size
            )
            
            # 处理拉伸撑满版本
            success_stretch = process_single_image_stretch(
                input_path, output_path_stretch, 
                bw_threshold, transparency_tolerance, 
                margin, target_size
            )
            
            if success_keep_ratio and success_stretch:
                print(f"已处理: {filename} -> 保持比例 & 拉伸版本")
                processed_count += 1
            else:
                print(f"处理失败或部分失败: {filename}")
    
    print(f"批量处理完成!成功处理 {processed_count} 张图片,生成两个版本")
 
if __name__ == "__main__":
    # 输入文件夹
    path=r'C:Usersjg2yXRZOneDrive桌面20251120模拟小孩照片并长大照片'
    # a = '00原图2'
    a = '04原图切边' 
    input_directory = path + fr'{a}'
    
    # 输出文件夹 - 保持比例版本
    output_directory_keep_ratio = os.path.join(path,  f"07{a[2:]}_黑白二色_透明背景_原比例")
    # 输出文件夹 - 拉伸撑满版本
    output_directory_stretch = os.path.join(path, f"08{a[2:]}_黑白二色_透明背景_拉伸")
    
    # 检查输入文件夹是否存在
    if not os.path.exists(input_directory):
        print(f"错误: 文件夹 '{input_directory}' 不存在")
    else:
        # 执行批量处理
        batch_process_images(
            input_directory, 
            output_directory_keep_ratio,
            output_directory_stretch,
            bw_threshold=128,           # 黑白化阈值(0-255)
            transparency_tolerance=30,  # 透明化容差
            margin=0,                   # 裁剪边距
            # target_size=(150, 250)      # 目标尺寸
            # target_size=(300, 500)      # 目标尺寸
            target_size=(1200, 1000)      # 目标尺寸
        )
        print("所有图片处理完成!")
        print(f"保持比例版本保存在: {output_directory_keep_ratio}")
        print(f"拉伸撑满版本保存在: {output_directory_stretch}")

只能提取黑白色块,不是线条

二、豆包写PS线条效果

用豆包改了十几次,终于获取了可以生成线条的效果代码



'''
照片转黑白线条
豆包,阿夏
20251128
'''
 
import cv2
import numpy as np
import matplotlib.pyplot as plt
import os  # 处理文件/文件夹
from pathlib import Path  # 更便捷的路径操作
 
def photo_to_stick_figure(image_path, output_path, radius=1):
    """
    将单张照片转为黑白简笔画线条(兼容中文路径)
    :param image_path: 输入照片路径(支持中文/英文)
    :param output_path: 输出简笔画路径
    :param radius: 最小值滤波半径(1-3像素最佳)
    """
    # ========== 读取图片(兼容中文路径) ==========
    try:
        img_bytes = np.fromfile(image_path, dtype=np.uint8)
        img = cv2.imdecode(img_bytes, cv2.IMREAD_COLOR)
        if img is None:
            raise FileNotFoundError(f"无法读取图片(文件损坏/格式错误):{image_path}")
    except Exception as e:
        print(f"【失败】{image_path} → {e}")
        return False  # 返回失败标识
    
    # 转RGB(OpenCV默认BGR)
    img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    
    # 2. 去色(灰度图)
    gray = cv2.cvtColor(img_rgb, cv2.COLOR_RGB2GRAY)
    
    # 3. 反相
    inverted = 255 - gray
    
    # 4. 最小值滤波(腐蚀操作)
    kernel = np.ones((radius*2+1, radius*2+1), np.uint8)
    min_filtered = cv2.erode(inverted, kernel)
    
    # 5. 颜色减淡混合(复刻PS效果)
    gray_float = gray.astype(np.float32)
    min_float = min_filtered.astype(np.float32)
    blend = np.divide(gray_float, 255 - min_float, out=np.zeros_like(gray_float), where=(255 - min_float)!=0) * 255
    blend = np.clip(blend, 0, 255).astype(np.uint8)
    
    # 6. 增强对比度(自动阈值)
    _, final = cv2.threshold(blend, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
    
    # ========== 保存图片(兼容中文路径) ==========
    try:
        # 自动匹配原文件后缀(png/jpg)
        suffix = Path(output_path).suffix.lower()
        encode_param = [int(cv2.IMWRITE_PNG_COMPRESSION), 0] if suffix == '.png' else [int(cv2.IMWRITE_JPEG_QUALITY), 95]
        result_bytes = cv2.imencode(suffix, cv2.cvtColor(final, cv2.COLOR_RGB2BGR), encode_param)[1]
        result_bytes.tofile(output_path)
        print(f"【成功】{image_path} → {output_path}")
        return True  # 返回成功标识
    except Exception as e:
        print(f"【失败】保存{output_path} → {e}")
        return False
 
def batch_process(input_dir, output_dir, radius=1):
    """
    批量处理文件夹内所有图片
    :param input_dir: 输入文件夹路径(如r"XXX123")
    :param output_dir: 输出文件夹路径(如r"XXX234")
    :param radius: 简笔画线条精细度
    """
    # 1. 检查输入文件夹是否存在
    input_path = Path(input_dir)
    if not input_path.exists():
        print(f"错误:输入文件夹不存在 → {input_dir}")
        return
    
    # 2. 创建输出文件夹(不存在则自动创建)
    output_path = Path(output_dir)
    output_path.mkdir(parents=True, exist_ok=True)
    
    # 3. 定义支持的图片格式(可扩展)
    support_formats = {'.jpg', '.jpeg', '.png', '.bmp', '.tiff'}
    
    # 4. 遍历输入文件夹内所有文件
    total = 0  # 总文件数
    success = 0  # 成功数
    fail = 0  # 失败数
    
    for file in input_path.iterdir():
        # 跳过文件夹,只处理文件
        if file.is_file():
            # 过滤非图片文件
            if file.suffix.lower() in support_formats:
                total += 1
                # 构建输出文件路径(同名保存)
                output_file = output_path / file.name
                # 调用单张处理函数
                if photo_to_stick_figure(str(file), str(output_file), radius):
                    success += 1
                else:
                    fail += 1
    
    # 5. 输出批量处理统计结果
    print("
=== 批量处理完成 ===")
    print(f"总文件数:{total} | 成功:{success} | 失败:{fail}")
    if fail > 0:
        print("请检查失败文件的路径/格式是否正确!")
 
# ------------------- 批量调用示例 -------------------
if __name__ == "__main__":
    # 替换为你的实际文件夹路径(支持中文!)
    path=r'C:Usersjg2yXRZOneDrive桌面20251120模拟小孩照片并长大照片'
    num=10
    batch_process(
       
        input_dir=path+r"0原图",  # 输入文件夹:123
        output_dir=path+fr"0PS修图{num}",  # 输出文件夹:234
        radius=num  # 线条精细度:1=精细,2=中等,3=粗犷
    )

不同线条粗细

提取了黑色、白色、部分灰色(只有线条粗细的区别),如果衣服是白色的,就看不见手臂的线条了。

三、豆包问原因:

结论显示

1. 通义万相(AI 生成)的逻辑:「重新绘制」所以很相似

它不是对原图做 “滤镜式处理”,而是基于 AI 模型的绘画能力

先识别原图的内容(小女孩、衣服图案、发型);再用 “简笔画风格”重新生成线条(比如主动简化背景、规整衣服图案的线条、优化头发的轮廓感);相当于 “AI 照着照片画了一幅简笔画”,线条更干净、风格更统一。

2. PS / 代码的逻辑:「基于原图的像素变换」不会补全线条

PS 的滤镜、代码的灰度 / 反相 / 滤波,都是对原图像素做数学运算

原图的杂色、背景纹理会被保留(比如背景的柜子、颜色渐变会变成杂乱的线条);只能 “提取原图已有的边缘”,无法主动 “简化 / 美化线条”(比如衣服图案的细节会因原图像素不足而模糊);相当于 “给照片加了个‘线稿滤镜’”,效果依赖原图的清晰度,且无法主动优化风格。

也就是只能通过AI绘画工具才能实现简笔画效果,靠Python批量PS效果做不到这么好的简笔画效果。

推荐就是电脑上,安装stable diffusion,或者不联网的图像处理工具,但是stable diffusion有9.32G,容量好大哦,就为了修几张幼儿照片,感觉不值得安装。

  • 全部评论(0)
手机二维码手机访问领取大礼包
返回顶部