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)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/925540.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Linux】TCP网络编程

目录 V1_Echo_Server V2_Echo_Server多进程版本 V3_Echo_Server多线程版本 V3-1_多线程远程命令执行 V4_Echo_Server线程池版本 V1_Echo_Server TcpServer的上层调用如下&#xff0c;和UdpServer几乎一样&#xff1a; 而在InitServer中&#xff0c;大部分也和UDP那里一样&…

如何选择适合的网站关键词?

在做谷歌SEO时&#xff0c;选对关键词是成功的一半。很多人以为找到搜索量大的词就可以了&#xff0c;但实际上&#xff0c;关键词选择并不只是看流量高低&#xff0c;更重要的是与你的用户需求是否匹配。要想精准定位用户&#xff0c;首先需要了解你的目标受众是谁&#xff0c…

AI-agent矩阵营销:让品牌传播无处不在

矩阵营销是一种通过多平台联动构建品牌影响力的策略&#xff0c;而 AI-agent 技术让这一策略变得更加智能化。AI社媒引流王凭借其矩阵管理功能&#xff0c;帮助品牌在多个平台上实现深度覆盖与精准传播。 1. 矩阵营销的优势 品牌触达更广&#xff1a;多平台联动可以覆盖不同用…

Vue教程|搭建vue项目|Vue-CLI新版脚手架

一、安装Node环境 安装Node及Npm环境 Node下载地址:Node.js — Run JavaScript EverywhereNode.js is a JavaScript runtime built on Chromes V8 JavaScript engine.https://nodejs.org/en/ 安装完成后,检查安装是否成功,并检查版本,命令如下: node -v npm -v mac@Macd…

简单好用的折线图绘制!

折线图的概念及作用&#xff1a; 折线图&#xff08;Line Chart&#xff09;是一种常见的图表类型&#xff0c;用于展示数据的变化趋势或时间序列数据。它通过一系列的数据点&#xff08;通常表示为坐标系中的点&#xff09;与这些点之间的线段相连&#xff0c;直观地展示变量…

模型 布鲁姆法则

系列文章 分享 模型&#xff0c;了解更多&#x1f449; 模型_思维模型目录。分层提升思维力。 1 布鲁姆法则的应用 1.1 布鲁姆法则在产品开发流程中的应用 背景&#xff1a; 在产品开发领域&#xff0c;创新和效率是关键。布鲁姆法则可以帮助产品经理和设计师系统地提升产品开…

如何通过实验室Lims系统解决效率和数据管理难题?

您的实验室是否还在为这些问题而苦恼呢&#xff1f; 提升企业业务效率&#xff1f; 质量可追溯、数据合规性&#xff1f; 提升客户服务质量&#xff1f; 如何让管理经验和检测数据有效积累? 实验室Lims系统功能 1、业务管理 2、检验管理 3、财务管理 4、客户管理 5、…

【目标跟踪】Anti-UAV数据集详细介绍

Anti-UAV数据集是在2021年公开的专用于无人机跟踪的数据集&#xff0c;该数据集采用RGB-T图像对的形式来克服单个类型视频的缺点&#xff0c;包含了318个视频对&#xff0c;并提出了相应的评估标准&#xff08;the state accurancy, SA)。 文章链接&#xff1a;https://arxiv.…

PyG教程:MessagePassing基类

PyG教程&#xff1a;MessagePassing基类 一、引言二、如何自定义消息传递网络1.构造函数2.propagate函数3.message函数4.aggregate函数5.update函数 三、代码实战1.图数据定义2.实现GNN的消息传递过程3.完整代码4.完整代码的精简版本 四、总结1.MessagePassing各个函数的执行顺…

Win10 系统下使用研华XNavi安装板卡驱动失败

配置&#xff1a;主板 AIMB-705G2&#xff0c;CPU i5-6500&#xff0c;系统 Windows10_64bit_Pro_22H2&#xff0c; 测试&#xff1a; 1、多次安装驱动。FAIL 2、尝试在其他电脑上移植板卡驱动并且使用数字签名安装。FAIL 3、系统更新到WIN10最新版本。FAIL 4、杀毒软件卸…

用三维模型的顶点法向量计算法线贴图

法线贴图的核心概念是在不增加额外多边形数目的情况下&#xff0c;通过模拟细节来改善光照效果。具体流程包括&#xff1a; 法线的计算与存储&#xff1a;通过法线映射将三维法线向量转化为法线贴图的 RGB 值。渲染中的使用&#xff1a;在片段着色器中使用法线贴图来替代原有的…

idea编译与maven编译的问题

先说下idea编译按钮的位置 编译运行时&#xff0c;会在idea底部出现Build面板 比较&#xff1a; idea编译器编译整个项目 maven编译器根据pom.xml的配置&#xff0c;可实现灵活编译 两套编译会遇到的问题&#xff1a; maven 编译成功 &#xff0c;但idea编译失败&#xff…

deepin 安装 chrome 浏览器

deepin 安装 chrome 浏览器 最近好多小伙伴儿和我说 deepin 无法安装最新的谷歌浏览器 其实是因为最新的 谷歌浏览器 其中的一个依赖需要提前安装 提前安装依赖然后再安装谷歌浏览器就可以了 安装 fonts-liberationsudo apt -y install fonts-liberation安装 chrome 浏览器sudo…

《String类》

目录 一、定义与概述 二、创建字符串对象 2.1 直接赋值 2.2 使用构造函数 三、字符串的不可变性 四、常用方法 4.1 String对象的比较 4.1.1 比较是否引用同一个对象 4.1.2 boolean equals(Object anObject)方法&#xff1a;按照字典序比较 4.1.3 int compareTo(Strin…

OpenSSH-9.9p1 OpenSSL-3.4.0 升级步骤详细

前言 收到漏洞扫描通知 OpenSSH 安全漏洞(CVE-2023-38408) OpenSSH 安全漏洞(CVE-2023-51385) OpenSSH 安全漏洞(CVE-2023-51384) OpenSSH 安全漏洞(CVE-2023-51767) OpenSSH 安全漏洞(CVE-2023-48795) OpenSSH&#xff08;OpenBSD SecureShell&#xff09;是加拿大OpenBSD计划…

【Stable Diffusion】安装教程

目录 一、python 安装教程 二、windows cuda安装教程 三、Stable Diffusion下载 四、Stable Diffusion部署&#xff08;重点&#xff09; 一、python 安装教程 &#xff08;1&#xff09;第一步下载 打开python下载页面&#xff0c;找到python3.10.9&#xff0c;点击右边…

Scala身份证上的秘密以及Map的遍历

object test {def main(args: Array[String]): Unit {val id "42032220080903332x"//1.生日是&#xff1f;//字符串截取val birthday id.substring(10,14) //不包括终点下标println(birthday)val year id.substring(6,10) //println(year)//性别&#xff1a;倒数第…

springboot 异步 @Async 的日常使用及失效场景

文章目录 springboot 异步 Async 的日常使用引言一、Async 使用位置二、Async 使用三、注解 Async 失效的情况&#xff08;1&#xff09;调用同一个类中的异步方法&#xff08;内部调用&#xff09;&#xff08;2&#xff09;未使用 EnableAsync 注解&#xff08;3&#xff09;…

Laravel8.5+微信小程序实现京东商城秒杀方案

一、商品秒杀涉及的知识点 鉴权策略封装掊口访问频次限制小程序设计页面防抖接口调用订单创建事务使用超卖防御 二、订单库存系统方案&#xff08;3种&#xff09; 下单减库存 优点是库存和订单的强一致性&#xff0c;商品不会卖超&#xff0c;但是可能导致恶意下单&#xff…

三角网格体的光滑性问题

三角网格体的光滑性问题 在计算机图形学和计算机辅助设计中&#xff0c;C0连续性&#xff08;也称为位置连续性&#xff09;是指两个曲线或曲面在它们的公共边界上具有相同的位置。这意味着它们在边界处没有缝隙或重叠&#xff0c;但它们的切线方向可以不同。C0连续性是最低级…