当前位置: 首页 > article >正文

python图像彩色数字化

效果展示:

目录结构:

alphabets.py

GENERAL = {
    "simple": "@%#*+=-:. ",
    "complex": "$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\|()1{}[]?-_+~<>i!lI;:,\"^`'. "
}
# Full list could be found here https://github.com/tsroten/zhon/tree/develop/zhon/cedict
CHINESE = {
    "standard": "龘䶑瀰幗獼鑭躙䵹觿䲔釅欄鐮䥯鶒獭鰽襽螻鰱蹦屭繩圇婹歜剛屧磕媿慪像僭堳噞呱棒偁呣塙唑浠唼刻凌咄亟拮俗参坒估这聿布允仫忖玗甴木亪女去凸五圹亐囗弌九人亏产斗丩艹刂彳丬了5丄三亻讠厂丆丨1二宀冖乛一丶、",
}

KOREAN = {
    "standard": "ㄱㄴㄷㄹㅁㅂㅅㅇㅈㅊㅋㅌㅍㅎㅏㅑㅓㅕㅗㅛㅜㅠㅡㅣ"
}

JAPANESE = {
    "hiragana": "あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよらりるれろわをん",
    "katakana": "アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワヲン"
}

ENGLISH = {
    "standard": "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz"
}

RUSSIAN = {
    "standard": "АаБбВвГгДдЕеЁёЖжЗзИиЙйКкЛлМмНнОоПпРрСсТтУуФфХхЦцЧчШшЩщЪъЫыЬьЭэЮюЯя"
}

GERMAN = {
    "standard": "AaÄäBbßCcDdEeFfGgHhIiJjKkLlMmNnOoÖöPpQqRrSsTtUuÜüVvWwXxYyZz"
}

FRENCH = {
    "standard": "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZzÆ挜ÇçÀàÂâÉéÈèÊêËëÎîÎïÔôÛûÙùŸÿ"
}

SPANISH = {
    "standard": "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZzÑñáéíóú¡¿"
}

ITALIAN = {
    "standard": "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZzÀÈàèéìòù"
}

PORTUGUESE = {
    "standard": "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZzàÀáÁâÂãÃçÇéÉêÊíÍóÓôÔõÕúÚ"
}

POLISH = {
    "standard": "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpRrSsTtUuWwYyZzĄąĘęÓóŁłŃńŻżŚśĆ揟"
}

app.py

import os
import hashlib
import argparse
import cv2
import numpy as np
from PIL import Image, ImageDraw, ImageOps
from flask import Flask, request, send_file, render_template_string
from utils import get_data

app = Flask(__name__)

# 确保上传和输出目录存在
if not os.path.exists("uploads"):
    os.makedirs("uploads")
if not os.path.exists("outputs"):
    os.makedirs("outputs")

# 文件处理常量
ALLOWED_EXTENSIONS = {'jpg', 'jpeg', 'png', 'gif', 'bmp'}
MAX_FILE_SIZE = 5 * 1024 * 1024  # 最大文件大小为5MB


def get_args():
    """
    获取命令行参数
    """
    parser = argparse.ArgumentParser("Image to ASCII")
    parser.add_argument("--input", type=str, default="uploads/input.jpg", help="输入图片路径")
    parser.add_argument("--output", type=str, default="outputs/output.jpg", help="输出图片路径")
    parser.add_argument("--language", type=str, default="english")  # 使用的字符语言
    parser.add_argument("--mode", type=str, default="standard")  # ASCII 模式
    parser.add_argument("--background", type=str, default="black", choices=["black", "white"],
                        help="背景颜色")
    parser.add_argument("--num_cols", type=int, default=300, help="输出图片的宽度字符数")
    parser.add_argument("--scale", type=int, default=2, help="输出图片的缩放比例")
    args = parser.parse_args()
    return args


def md5_filename(filename):
    """返回文件名的MD5哈希值,用于生成唯一的文件名"""
    hash_md5 = hashlib.md5()
    hash_md5.update(filename.encode('utf-8'))  # 更新哈希值
    return hash_md5.hexdigest()


def allowed_file(filename):
    """检查文件是否具有允许的扩展名"""
    return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS


def convert_image_to_ascii(opt):
    """
    将图像转换为ASCII字符图像
    :param opt: 命令行参数对象,包含转换的配置
    """
    # 根据背景颜色设置背景
    if opt.background == "white":
        bg_code = (255, 255, 255)
    else:
        bg_code = (0, 0, 0)

    # 获取字符列表、字体和缩放参数
    char_list, font, sample_character, scale = get_data(opt.language, opt.mode)
    num_chars = len(char_list)
    num_cols = opt.num_cols

    # 检查输入文件是否存在
    if not os.path.exists(opt.input):
        print(f"错误:文件 {opt.input} 不存在!")
        return

    # 读取图像并验证
    image = cv2.imread(opt.input, cv2.IMREAD_COLOR)
    if image is None:
        print(f"错误:无法加载图片 {opt.input}")
        return

    # 将图像从BGR格式转换为RGB格式
    image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)

    # 获取图像尺寸
    height, width, _ = image.shape
    cell_width = width / opt.num_cols
    cell_height = scale * cell_width
    num_rows = int(height / cell_height)

    # 如果列数或行数过多,使用默认设置
    if num_cols > width or num_rows > height:
        print("列数或行数过多,使用默认设置。")
        cell_width = 6
        cell_height = 12
        num_cols = int(width / cell_width)
        num_rows = int(height / cell_height)

    # 获取字符的宽度和高度
    char_width, char_height = font.getsize(sample_character)
    out_width = char_width * num_cols
    out_height = scale * char_height * num_rows

    # 创建输出图像
    out_image = Image.new("RGB", (out_width, out_height), bg_code)
    draw = ImageDraw.Draw(out_image)

    # 逐个处理图像区域,转换为字符
    for i in range(num_rows):
        for j in range(num_cols):
            partial_image = image[int(i * cell_height):min(int((i + 1) * cell_height), height),
                            int(j * cell_width):min(int((j + 1) * cell_width), width), :]
            partial_avg_color = np.sum(np.sum(partial_image, axis=0), axis=0) / (cell_height * cell_width)
            partial_avg_color = tuple(partial_avg_color.astype(np.int32).tolist())

            # 根据平均色值选择合适的字符
            char = char_list[min(int(np.mean(partial_image) * num_chars / 255), num_chars - 1)]
            draw.text((j * char_width, i * char_height), char, fill=partial_avg_color, font=font)

    # 根据背景颜色裁剪图像
    if opt.background == "white":
        cropped_image = ImageOps.invert(out_image).getbbox()
    else:
        cropped_image = out_image.getbbox()

    out_image = out_image.crop(cropped_image)
    out_image.save(opt.output)


@app.route('/')
def index():
    """
    渲染首页HTML,用户可以上传图片
    """
    return render_template_string("""
        <html>
            <head>
                <style>
                    body {
                        font-family: Arial, sans-serif;
                        background-color: #f4f4f9;
                        margin: 0;
                        padding: 0;
                        display: flex;
                        justify-content: center;
                        align-items: center;
                        height: 100vh;
                    }
                    .container {
                        background-color: #fff;
                        padding: 30px;
                        border-radius: 8px;
                        box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
                        width: 300px;
                        text-align: center;
                    }
                    h1 {
                        font-size: 24px;
                        color: #333;
                    }
                    form {
                        margin-top: 20px;
                    }
                    input[type="file"] {
                        display: block;
                        margin: 10px auto;
                        padding: 10px;
                        border: 1px solid #ccc;
                        border-radius: 4px;
                        width: 100%;
                    }
                    input[type="submit"] {
                        background-color: #4CAF50;
                        color: white;
                        border: none;
                        padding: 12px 20px;
                        border-radius: 4px;
                        cursor: pointer;
                        width: 100%;
                        font-size: 16px;
                    }
                    input[type="submit"]:hover {
                        background-color: #45a049;
                    }
                    .footer {
                        margin-top: 20px;
                        font-size: 14px;
                        color: #777;
                    }
                </style>
            </head>
            <body>
                <div class="container">
                    <h1>彩色图片转化器YFREE</h1>
                    <form action="/upload" method="POST" enctype="multipart/form-data">
                        <input type="file" name="image" required>
                        <input type="submit" value="上传图片">
                    </form>
                    <div class="footer">
                        <p>支持开源 <a href="https://github.com/vietnh1009/ASCII-generator" target="_blank">github</a></p>
                    </div>
                </div>
            </body>
        </html>
    """)


@app.route('/upload', methods=['POST'])
def upload():
    """
    处理上传的图片,将其转换为ASCII图像并返回
    """
    if 'image' not in request.files:
        return '没有文件上传'

    file = request.files['image']
    if file.filename == '':
        return '没有选择文件'

    # 检查文件扩展名
    if not allowed_file(file.filename):
        return '无效的文件类型,请上传图片。'

    # 检查文件大小
    file.seek(0, os.SEEK_END)
    file_size = file.tell()
    if file_size > MAX_FILE_SIZE:
        return '文件太大,最大支持文件大小为5MB。'
    file.seek(0)  # 重置文件指针

    # 保存文件
    md5_filename_value = md5_filename(file.filename)
    filename = os.path.join("uploads", md5_filename_value + os.path.splitext(file.filename)[1])
    file.save(filename)

    if not os.path.exists(filename):
        return '文件上传失败。'

    # 获取参数并处理图像
    opt = get_args()
    opt.input = filename
    opt.output = os.path.join("outputs", f"output_{md5_filename_value}.jpg")

    # 调用转换函数
    convert_image_to_ascii(opt)

    return send_file(opt.output, as_attachment=True)


if __name__ == '__main__':
    app.run(port=8979)

Dockerfile 和 requestments.txt

# 使用python的官方镜像作为基础镜像
FROM python:3.6-slim

# 设置工作目录
WORKDIR /app

# 复制requirements.txt到容器中
COPY requirements.txt .

# 安装python依赖
RUN pip install --no-cache-dir -r requirements.txt

# 复制项目的所有文件到容器
COPY . .

# 暴露端口
EXPOSE 8979

# 指定启动命令
CMD ["python", "app.py"]


click==8.0.4
colorama==0.4.5
dataclasses==0.8
Flask==2.0.3
importlib-metadata==4.8.3
itsdangerous==2.0.1
Jinja2==3.0.3
MarkupSafe==2.0.1
numpy==1.19.5
opencv-contrib-python==4.6.0.66
Pillow==8.4.0
typing_extensions==4.1.1
Werkzeug==2.0.3
zipp==3.6.0
opencv-python-headless

utils.py

import numpy as np
from PIL import Image, ImageFont, ImageDraw, ImageOps


def sort_chars(char_list, font, language):
    if language == "chinese":
        char_width, char_height = font.getsize("制")
    elif language == "korean":
        char_width, char_height = font.getsize("ㅊ")
    elif language == "japanese":
        char_width, char_height = font.getsize("あ")
    elif language in ["english", "german", "french", "spanish", "italian", "portuguese", "polish"]:
        char_width, char_height = font.getsize("A")
    elif language == "russian":
        char_width, char_height = font.getsize("A")
    num_chars = min(len(char_list), 100)
    out_width = char_width * len(char_list)
    out_height = char_height
    out_image = Image.new("L", (out_width, out_height), 255)
    draw = ImageDraw.Draw(out_image)
    draw.text((0, 0), char_list, fill=0, font=font)
    cropped_image = ImageOps.invert(out_image).getbbox()
    out_image = out_image.crop(cropped_image)
    brightness = [np.mean(np.array(out_image)[:, 10 * i:10 * (i + 1)]) for i in range(len(char_list))]
    char_list = list(char_list)
    zipped_lists = zip(brightness, char_list)
    zipped_lists = sorted(zipped_lists)
    result = ""
    counter = 0
    incremental_step = (zipped_lists[-1][0] - zipped_lists[0][0]) / num_chars
    current_value = zipped_lists[0][0]
    for value, char in zipped_lists:
        if value >= current_value:
            result += char
            counter += 1
            current_value += incremental_step
        if counter == num_chars:
            break
    if result[-1] != zipped_lists[-1][1]:
        result += zipped_lists[-1][1]
    return result


def get_data(language, mode):
    if language == "general":
        from alphabets import GENERAL as character
        font = ImageFont.truetype("fonts/DejaVuSansMono-Bold.ttf", size=20)
        sample_character = "A"
        scale = 2
    elif language == "english":
        from alphabets import ENGLISH as character
        font = ImageFont.truetype("fonts/DejaVuSansMono-Bold.ttf", size=20)
        sample_character = "A"
        scale = 2
    elif language == "german":
        from alphabets import GERMAN as character
        font = ImageFont.truetype("fonts/DejaVuSansMono-Bold.ttf", size=20)
        sample_character = "A"
        scale = 2
    elif language == "french":
        from alphabets import FRENCH as character
        font = ImageFont.truetype("fonts/DejaVuSansMono-Bold.ttf", size=20)
        sample_character = "A"
        scale = 2
    elif language == "italian":
        from alphabets import ITALIAN as character
        font = ImageFont.truetype("fonts/DejaVuSansMono-Bold.ttf", size=20)
        sample_character = "A"
        scale = 2
    elif language == "polish":
        from alphabets import POLISH as character
        font = ImageFont.truetype("fonts/DejaVuSansMono-Bold.ttf", size=20)
        sample_character = "A"
        scale = 2
    elif language == "portuguese":
        from alphabets import PORTUGUESE as character
        font = ImageFont.truetype("fonts/DejaVuSansMono-Bold.ttf", size=20)
        sample_character = "A"
        scale = 2
    elif language == "spanish":
        from alphabets import SPANISH as character
        font = ImageFont.truetype("fonts/DejaVuSansMono-Bold.ttf", size=20)
        sample_character = "A"
        scale = 2
    elif language == "russian":
        from alphabets import RUSSIAN as character
        font = ImageFont.truetype("fonts/DejaVuSansMono-Bold.ttf", size=20)
        sample_character = "Ш"
        scale = 2
    elif language == "chinese":
        from alphabets import CHINESE as character
        font = ImageFont.truetype("fonts/simsun.ttc", size=10)
        sample_character = "制"
        scale = 1
    elif language == "korean":
        from alphabets import KOREAN as character
        font = ImageFont.truetype("fonts/arial-unicode.ttf", size=10)
        sample_character = "ㅊ"
        scale = 1
    elif language == "japanese":
        from alphabets import JAPANESE as character
        font = ImageFont.truetype("fonts/arial-unicode.ttf", size=10)
        sample_character = "お"
        scale = 1
    else:
        print("Invalid language")
        return None, None, None, None
    try:
        if len(character) > 1:
            char_list = character[mode]
        else:
            char_list = character["standard"]
    except:
        print("Invalid mode for {}".format(language))
        return None, None, None, None
    if language != "general":
        char_list = sort_chars(char_list, font, language)

    return char_list, font, sample_character, scale

支持开源:

vietnh1009/ASCII-generator: ASCII generator (image to text, image to image, video to video) (github.com)


http://www.kler.cn/a/415617.html

相关文章:

  • redis命令 及 redis 常见的数据结构
  • Android Studio 配置 proto
  • 面试小札:JVM虚拟机
  • PHP 生成分享海报
  • AI潮汐日报1128期:Sora泄露引发争议、百度早期研究对AI领域Scaling Law的贡献、Meta发布系列AI开源项目
  • 26页PDF | 数据中台能力框架及评估体系解读(限免下载)
  • PVE相关名词通俗表述方式———多处细节实验(方便理解)
  • 实践五 网络安全防范技术
  • Android复习代码1-4章
  • Codigger Desktop:多样 Look 设计,全新 Game Look 带来趣味体验
  • 数据结构——哈夫曼编码
  • 鸿蒙学习相关术语
  • 如何画出漂亮的决策树?
  • 【maven-4】IDEA 配置本地 Maven 及如何使用 Maven 创建 Java 工程
  • 自动类型推导(auto 和 decltype);右值引用和移动语义
  • mysql8.0基础-锁基础(七)
  • neo4j desktop版命令行中导入导出dump
  • Unity之一键创建自定义Package包
  • 题目 3209: 蓝桥杯2024年第十五届省赛真题-好数
  • 信息学奥赛一本通 1448:【例题1】电路维修 | 洛谷 P4667 [BalticOI 2011 Day1] Switch the Lamp On 电路维修
  • 《使用Python进行数据挖掘:理论、应用与案例研究》
  • spine 动画层 动态权重
  • brew安装mongodb和php-mongodb扩展新手教程
  • 智启未来 扬帆5G:江苏移动打造“5G + 智慧教育”典范,引领教育新风尚
  • 个人博客接入github issue风格的评论,utteranc,gitment
  • Nuxt.js 应用中的 render:response 事件钩子