Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

生成的字体出现部分下方空白面积较大,以及笔画不够平滑(像素风)的一个可能的整合解决方案/办法 #96

Closed
AyTsaoIce opened this issue Sep 17, 2024 · 3 comments

Comments

@AyTsaoIce
Copy link

AyTsaoIce commented Sep 17, 2024

首先感谢项目的大佬们!ヾ(≧▽≦*)o

没有大佬们的成果、教程、总结等,也没有撰写此issue时的整合解决方案/方法的出现,或者实现方式也更为复杂,衷心感谢项目的各位大佬们。
在某一方面,借助了这个项目解决了当时的一些燃眉之急,再次感谢提及的所有项目的大佬们ヾ(≧▽≦*)o

参考的一部分教程

#78 (comment) - 教程:从手写字体到A4纸打印(GPU篇)
#43 (comment) - 教程:如何生成自己的个性化手写文字?
#59 (comment) - 有没有可以自行解决输出字体狂草风格的方法

遇到的问题及自己的解决方案/办法

1 - 遇到的问题

  1. 生成的字体锯齿状比较明显。
    图片
  2. 生成的部分字体整体位置偏上,在下方留出较大的空白位置,虽然能够一定程度上创造出手写的字高低不一的效果,但倘若换成了方格纸或者带有横线的纸张,则可能显得较为突兀。
    图片
    图片

如上图,其中的「池」、「弛」、「驰」等整体位置偏上,在下方留出较大的空白位置。
如下图,背景换成方格纸时,显得较为突兀。

  1. 教程:从手写字体到A4纸打印(GPU篇) #78 (comment) 中「Part6.手写模拟生成」推荐的项目,在需要特定纸张的情况(例如,需要写在带有横线的信纸上),调整背景比较费力。

2 -可能的整合解决方案/办法

由于个人能力精力有限,所以不一定是最好的整合解决方案/办法,欢迎进行讨论╰(°▽°)╯(虽然可能因为忙个人不太能回复qwq)
限于时间精力有限,目前这边暂且只在Windows 11 家庭中文版23H2进行了实践。
本人在执行的时候一直提示PermissionError: [Errno 13] Permission denied:,用管理员模式运行之后倒是没有报这个错了。

2.1 生成的字体锯齿状比较明显

2.1.1 效果

先来看一下使用该整合方案的效果:
图片

如图,左边为直接生成的字体,右边为经过该方案多次平滑后的字体,能看到锯齿的减少。

图片

大部分文字的生成效果较好。

2.1.2 方案/方法

目前只想到了通过超分辨率的方法实现平滑,考虑到需要兼顾线条平滑与现实的符合,觉得可能相对而言,以目前个人的水平和环境,采用超分辨率的方式较为开箱即用。

2.1.2.1 将下列代码保存为{你想取的名字}.py并保存。

可以先确保Windows文件资源管理器中能够显示文件的扩展名。

本人代码写的很烂,大佬们轻喷> <

import os
import subprocess

# 视情况将路径末尾的反斜杠补全
def check_path(path):
    if path=="":
        pass
    elif not path.endswith("\\"):
        path=path+"\\"
    return path

# 提取文件名及其不带「.」的后缀名
def spilt_file_suffix(file):
    file = file.split(".")
    file_without_suffix = ""
    for i in file[:-1]:
        file_without_suffix = file_without_suffix + i + "."
    return file_without_suffix[:-1], file[-1]

# 使用Real-ESRGAN对图像进行超分,-s后为放大倍速,可调整
def exe_realesrgan(input_path_file):
    command_line = f"{realesrgan_path[:-1]} -i \"{input_path_file}\" -o \"{input_path_file}\" -s 4" # 使用「realesrgan_path[:-1]」是为了去除其末尾的「\」字符
    # 命令的执行
    print(f"【正在执行命令】{command_line}")
    # print(os.system(command_line))
    result = subprocess.run(['powershell','/c',command_line],capture_output=True)
    out = result.stderr.decode('gbk') # ⚠ 如果执行出现关于这行的错误,修改为「utf-8」或者别的编码试试
    # out = result.stderr
    print(out)

# 使用FFmpeg将超分后的图片缩放为原图片像素尺寸
def exe_ffmpeg(i, input_path):
    # 由于FFmpeg好像不支持设置输入输出文件为同一个,所以需要创建临时的文件,删除原文件后再重命名,以达到「替换」文件的效果
    output_ffmpeg_temp_filename, output_ffmpeg_temp_suffix = spilt_file_suffix(i) # 提取文件名及其不带「.」的后缀名
    output_ffmpeg_temp =f"{input_path}{output_ffmpeg_temp_filename}_ffmpeg.{output_ffmpeg_temp_suffix}" # 拼凑出临时文件的绝对路径
    command_line = f"ffmpeg -i \"{input_path_file}\" -vf scale=256:256 \"{output_ffmpeg_temp}\""
    # 命令的执行
    print(f"【正在执行命令】{command_line}")
    print(os.system(command_line))
    # 删除原文件,重命名临时文件
    os.remove(input_path + i)
    os.rename(output_ffmpeg_temp, input_path + i)

# 判断用户输入的次数是否是纯数字
def is_number_exception(s):
    try:
        int(s)
        return s
    except:
        print("【输入的不是整数,将退出。")
        exit()
        return False

print(r"""==============================
      
⚠请预先自行对存有待转换的图像文件所在的文件夹进行备份。

借助了Real-ESRGAN实现文字平滑,
需要提前下载好「Real-ESRGAN」的Windows绿色便携版,并尽量不作改动。
(https://github.com/xinntao/Real-ESRGAN)
      
使用了FFmpeg用于调整图片像素尺寸,
需要提前下载好「FFmpeg」,并添加进「系统环境变量」。
(https://ffmpeg.org/)
==============================
      
【请输入下载好的「Real-ESRGAN」的Windows绿色便携版exe可执行文件绝对路径(包括.exe)】
例如:D:\XXX\realesrgan-ncnn-vulkan-20220424-windows\realesrgan-ncnn-vulkan.exe""")
realesrgan_path = check_path(input("【可执行文件绝对路径】"))
input_path = check_path(input("【请输入待转换的图像文件所在路径(请在该目录只留有待处理的图像文件)】"))
input_times = 1 # 设置默认次数
input_times = int(is_number_exception(input("【请输入想要平滑操作进行的次数(仅整数)】")))
path_input_forUse=os.listdir(input_path) # 提取路径下的所有文件列表

# for i in path_input_forUse:
#     # 拼凑出输入文件的绝对路径
#     input_path_file = input_path + i
#     # 按设定好的次数进行循环处理
#     for c in range(input_times):
#         # 使用Real-ESRGAN对图像进行超分
#         exe_realesrgan(input_path_file)
#         # 使用FFmpeg将超分后的图片缩放为原图片像素尺寸
#         exe_ffmpeg(i, input_path)

# 按设定好的次数进行循环处理
for c in range(input_times):
    # 使用Real-ESRGAN对图像进行超分
    exe_realesrgan(input_path)
    for i in path_input_forUse:
        # 拼凑出输入文件的绝对路径
        input_path_file = input_path + i
        # 使用FFmpeg将超分后的图片缩放为原图片像素尺寸
        exe_ffmpeg(i, input_path)
input("【√】已经执行完毕,回车以退出。")
2.1.2.2 打开cmd或者powershell,输入以下内容并回车,一般情况下保留双引号大抵能够避免一些文件目录路径上的错误。
python "{刚刚保存的Python文件的绝对路径}.py"
2.1.2.3 根据提示配置好环境。
==============================
      
⚠请预先自行对存有待转换的图像文件所在的文件夹进行备份。

借助了Real-ESRGAN实现文字平滑,
需要提前下载好「Real-ESRGAN」的Windows绿色便携版,并尽量不作改动。
(https://github.com/xinntao/Real-ESRGAN)
      
使用了FFmpeg用于调整图片像素尺寸,
需要提前下载好「FFmpeg」,并添加进「系统环境变量」。
(https://ffmpeg.org/)
==============================

下载好并解压到合适的位置。

2.1.2.4 根据提示分别输入「可执行文件绝对路径」、「待转换的图像文件所在路径(请在该目录只留有待处理的图像文件)」、「想要平滑操作进行的次数(仅整数)」。
2.1.2.5 等待执行完毕。

2.2 生成的部分字体整体位置偏上,在下方留出较大的空白位置

一开始想直接在FontForge上看看有没有相关的功能的,或者方不方便通过脚本直接实现调整,但限于个人的水平和时间精力所限,还是选择了在 #78 (comment) 的「Part4.生成图处理」环节之前直接对生成的图片文件进行处理。

实现方式是「从上往下、从左到右按行扫描像素,出现了黑色像素则跳过,没有出现黑色像素但该行下方又出现了黑色像素则也跳过,没有出现黑色像素且该行下方也没有出现黑色像素则以最开始检测到出现『没有出现黑色像素且该行下方也没有出现黑色像素』情况的那行开始往下一直到最后一行,计算出这几行像素在整个图像文件的行像素中所占的比例」,然后根据测出的比例确认纵向拉伸对应数值,并在拉伸后裁剪回原图片的尺寸。

2.2.1 将下列代码保存为{你想取的名字}.py并保存。

可以先确保Windows文件资源管理器中能够显示文件的扩展名。

本人代码写的很烂,大佬们轻喷> <

from PIL import Image
import os

# 视情况将路径末尾的反斜杠补全
def check_path(path):
    if path=="":
        pass
    elif not path.endswith("\\"):
        path=path+"\\"
    return path

# 获取像素行空白部分所占的比例
def calculate_white_row_ratio(image_path):
    # 打开图片并获取图片的宽度和高度
    img = Image.open(image_path)
    width, height = img.size
    # 将图片转换为灰度模式,简化处理
    img = img.convert('L')
    # 获取图像数据
    pixels = img.load()
    # 初始化计数器
    total_rows = height
    valid_white_rows_count = 0
    # 遍历每一行
    for y in range(height):
        # 检查该行是否有黑色像素
        has_black_pixel = False
        for x in range(width):
            if pixels[x, y] < 1:  # 假设黑色像素值小于1(可以根据实际情况调整)
                has_black_pixel = True
                break
        # 如果该行有黑色像素,则跳过
        if has_black_pixel:
            continue
        # 检查该行下方是否存在黑色像素
        has_black_below = any(pixels[x, y1] < 1 for x in range(width) for y1 in range(y + 1, height))
        # 如果该行下方有黑色像素,则跳过
        if has_black_below:
            continue
        # 如果没有黑色像素且下方也没有黑色像素,则记录为有效行
        valid_white_rows_count += 1
    # 计算比例
    ratio = valid_white_rows_count / total_rows if total_rows > 0 else 0
    print(f"Ratio of rows without black pixels and no black pixels below them: {ratio}")
    return ratio

# 根据以像素行空白部分所占的比例测算出需要拉伸的比例
def ratio_trans(ratio):
    ratio = 1 - ratio
    ratio = 1 / ratio
    print(f"ratio_trans:{ratio}")
    return ratio

# 对图片进行拉伸,并且裁剪为原图像素数和尺寸
def stretch_image_vertically(image_path, ratio):
    # 打开图片
    img = Image.open(image_path)
    width, height = img.size
    # 计算新的高度,考虑到「ratio」变量 小于1时会反向纵向拉伸,所以选择用max(1, ratio)
    # new_height = height * (1+max(1, ratio))
    new_height = int(height * (max(0, ratio)))
    # 对图像拉伸
    img = img.resize((width, new_height), resample=Image.NEAREST)
    # 对图像裁剪,裁剪为原尺寸
    img = img.crop((0,0,width,height))
    # # 展示拉伸后的图像
    # img.show()

    return img

# 去除文件名后缀名,包括后缀名的「.」号,并返回文件名及不带「.」的后缀名
def spilt_file_suffix(file):
    file = file.split(".")
    file_without_suffix = ""
    for i in file[:-1]:
        file_without_suffix = file_without_suffix + i + "."
    return file_without_suffix[:-1], file[-1]

# 拼凑出「输出文件目录+文件名」
def output_path_rename(i, output_path, prefix, suffix):
    i_filename, i_suffix = spilt_file_suffix(i)
    output_path = f"{output_path}{prefix}{i_filename}{suffix}.{i_suffix}"
    return output_path

# 使用示例
input_path = check_path(input("请输入待转换的图像所在路径(请在该目录只留有待处理的图像文件):")) # 获取待处理图像所在路径
output_path = check_path(input("请输入图像转换后的输出路径:")) # 获取图像输出路径
prefix = input("请输入期望在输出的文件名中添加的前缀:") 
suffix = input("请输入期望在输出的文件名中添加的后缀:")
path_input_forUse=os.listdir(input_path) # 提取路径下的所有文件列表
for i in path_input_forUse:
    input_path_file = input_path+i # 拼凑出输入文件的绝对路径
    output_path_file = output_path_rename(i, output_path, prefix, suffix) # 拼凑出输出文件的绝对路径

    print(f"【Directory】{input_path_file}")

    ratio = calculate_white_row_ratio(input_path_file) # 获取像素行空白部分所占的比例
    ratio = ratio_trans(ratio) # 根据以像素行空白部分所占的比例测算出需要拉伸的比例
    stretch_image_vertically(input_path_file, ratio).save(output_path_file) # 对图片进行拉伸,并且裁剪为原图像素数和尺寸

    print("\n")
input("【√】已经执行完毕,回车以退出。")

2.2.2 打开cmd或者powershell,输入以下内容并回车,一般情况下保留双引号大抵能够避免一些文件目录路径上的错误。

python "{刚刚保存的Python文件的绝对路径}.py"

2.2.3 根据提示分别输入「待转换的图像所在路径(请在该目录只留有待处理的图像文件)」、「图像转换后的输出路径期望在输出的文件名中添加的前缀」、「期望在输出的文件名中添加的后缀」,随后等待执行完毕。

2.3 使用教程推荐的项目,调整背景比较费力

各个项目都有各自的特点,而且个人觉得目前在此方面也没有什么高低之分(叠个甲qwq),个人使用的是另外一个项目(https://github.com/why20021008/hand-write ),支持自定义背景图和预览,详细视频教程在阿B有(https://www.bilibili.com/video/BV1Kr4y1s7Yu ),如果有相关的需求,觉得这个可能更加适合自己的需求的,或许可以尝试。

@AyTsaoIce
Copy link
Author

「2.2 生成的部分字体整体位置偏上,在下方留出较大的空白位置」用的方式比较简单粗暴,因此会导致像「一」这样的字可能会被拉伸过度,目前想到的改进方案是结合已有的ttf文件,分别按字导出为一个个图片格式文件,然后测量横向和纵向留白占比用以作为生成字体拉伸的参考,不过个人应该没有什么时间精力做了qwq,如果有改动的话可能会重新编辑这个issue,也坐等大佬们给出可能的更好更便捷的方案(/ω\)

@dailenson
Copy link
Owner

关于像素风的问题,有个很简单的解决方案,直接把下面这行代码的width和height改大一点即可,例如都改成1024。

SDT/test.py

Line 94 in 2d72dc2

sk_pil = coords_render(preds[i], split=True, width=256, height=256, thickness=8, board=0)

@AyTsaoIce
Copy link
Author

AyTsaoIce commented Sep 18, 2024

关于像素风的问题,有个很简单的解决方案,直接把下面这行代码的width和height改大一点即可,例如都改成1024。

SDT/test.py

Line 94 in 2d72dc2

sk_pil = coords_render(preds[i], split=True, width=256, height=256, thickness=8, board=0)

感谢大佬☆*: .。. o(≧▽≦)o .。.:*☆这个方法确实简单的多233

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants