python图像彩色数字化

news2025/1/15 15:46:54

效果展示:

目录结构:

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.coloradmin.cn/o/2250811.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

欧科云链研究院:比特币还能“燃”多久?

出品&#xff5c; OKG Research 作者&#xff5c;Hedy Bi 本周二&#xff0c;隔夜“特朗普交易” 的逆转趋势波及到比特币市场。比特币价格一度冲高至约99,000美元后迅速回落至93,000美元以下&#xff0c;最大跌幅超6%。这是由于有关以色列和黎巴嫩有望达成停火协议的传闻引发…

Unity之一键创建自定义Package包

内容将会持续更新&#xff0c;有错误的地方欢迎指正&#xff0c;谢谢! Unity之一键创建自定义Package包 TechX 坚持将创新的科技带给世界&#xff01; 拥有更好的学习体验 —— 不断努力&#xff0c;不断进步&#xff0c;不断探索 TechX —— 心探索、心进取&#xff01; …

E2、UML类图顺序图状态图实训

一、实验目的 在面向对象的设计里面&#xff0c;可维护性复用都是以面向对象设计原则为基础的&#xff0c;这些设计原则首先都是复用的原则&#xff0c;遵循这些设计原则可以有效地提高系统的复用性&#xff0c;同时提高系统的可维护性。在掌握面向对象七个设计原则基础上&…

【Java基础入门篇】一、变量、数据类型和运算符

Java基础入门篇 一、变量、数据类型和运算符 1.1 变量 计算机中的数据表示方式是&#xff1a;“二进制(0/1)”&#xff0c;但是同时也可以兼容其他进制&#xff0c;例如八进制、十进制、十六进制等。 Java变量的本质是&#xff1a;存储在固定空间的内容&#xff0c;变量名是…

前端学习笔记之文件下载(1.0)

因为要用到这样一个场景&#xff0c;需要下载系统的使用教程&#xff0c;所以在前端项目中就提供了一个能够下载系统教程的一个按钮&#xff0c;供使用者进行下载。 所以就试着写一下这个功能&#xff0c;以一个demo的形式进行演示&#xff0c;在学习的过程中也发现了中文路径…

【力扣】387.字符串中的第一个唯一字符

问题描述 思路解析 对于这种个数有限的问题&#xff0c;我的第一想法是使用桶排序来解决因为s中只有小写英文单词&#xff0c;所以我只需要一个26个单位的数组就好了通过 charAt() 来提取单个字符&#xff0c;然后通过 -‘a’ 来将其映射到 0~25&#xff0c;不然数组长度就被超…

数据采集中,除了IP池的IP被封,还有哪些常见问题?

在数据采集的过程中&#xff0c;代理IP池的使用无疑为我们打开了一扇通往信息宝库的大门。然而&#xff0c;除了IP被封禁这一常见问题外&#xff0c;还有许多其他问题可能影响数据采集的效果。本文将探讨在数据采集中&#xff0c;除了IP被封之外&#xff0c;还可能遇到的一些常…

【笔记】自动驾驶预测与决策规划_Part8_数据驱动的规划方法

文章目录 0. 前言1.生成模型1.1 Diffusion-ES1. Diffusion-ES算法介绍2. Diffusion-ES算法具体流程Diffusion Model 是什么&#xff1f;Diffusion-ES: Evolutionary StrategiesDiffusion-ES MethodDiffusion-ES Mapping Language instructions to reward functions with LLM pr…

里氏替换原则:Java面向对象设计的基石

在面向对象编程&#xff08;OOP&#xff09;中&#xff0c;继承是一个强大的工具&#xff0c;它允许我们创建新的类&#xff08;子类&#xff09;来复用和扩展现有类&#xff08;父类&#xff09;的功能。然而&#xff0c;继承也带来了复杂性&#xff0c;特别是在确保子类能够正…

C++笔记之单例模式与静态方法的使用辨析及代码规范

C++笔记之单例模式与静态方法的使用辨析及代码规范 code review! 文章目录 C++笔记之单例模式与静态方法的使用辨析及代码规范一.示例代码二.讲解2.1.代码规范2.1.1.单例模式实现2.1.2.静态方法实现2.1.3.单例模式结合静态方法2.2.总结一.示例代码 // 使用 set 方法设置值(通…

18:(标准库)DMA二:DMA+串口收发数据

DMA串口收发数据 1、DMA串口发送数据2、DMA中断串口接收定长数据包3、串口空闲中断DMA接收不定长数据包4、串口空闲中断DMA接收不定长数据包DMA发送数据包 1、DMA串口发送数据 当串口的波特率大于115200时&#xff0c;可以通过DMA1进行数据搬运&#xff0c;以防止数据的丢失。如…

加载不同本地gltf模型,模型内容不更新的解决方案

相关链接 http://mars3d.cn/editor-vue.html?keyex_6_2_2&idlayer-graphic/draw/draw-model 问题内容 加载本地gltf模型的时候&#xff0c;不clear图层&#xff0c;再打开其他本地gltf&#xff0c;gltf的内容就不更新 重现步骤 进入官网示例&#xff0c;贴入以下代码…

可视化建模以及UML期末复习篇----相关软件安装

作为一个过来人&#xff0c;我的建议是别过来。 一、可视化建模 <1>定义: 官方&#xff1a;一种使用图形符号来表示系统结构和行为的建模技术。 我&#xff1a;其实说白了就是把工作流程用图形画出来。懂不&#xff1f; <2>作用: 提高理解和分析复杂系统的能力。促…

AI开发 - GPT之魂 用Python 演示chatGPT的自注意力机制 - 机器学习

自注意力机制&#xff08;Self-Attention&#xff09;就是让模型在处理每个词时&#xff0c;学会“关注重点”&#xff0c;而不是平均地对每个词一视同仁。这种机制让 GPT 能更聪明地理解句子的上下文和语义之间的关系。 自注意力机制是 GPT 的核心&#xff0c;它帮助模型在理解…

Web 表单开发全解析:从基础到高级掌握 HTML 表单设计

文章目录 前言一、什么是 Web 表单?二、表单元素详解总结前言 在现代 Web 开发中,表单 是用户与后端服务交互的重要桥梁。无论是用户登录、注册、搜索,还是提交反馈,表单都无处不在。在本文中,我们将从基础入手,全面解析表单的核心知识点,并通过示例带你轻松掌握表单开…

HCIE:详解OSPF,从基础到高级特性再到深入研究

目录 前言 一、OSPF协议基本原理 简介 基本原理 OSPF路由器类型 OSPF网络类型 OSPF报文类型和封装 OSPF邻居的建立的维护 DR和BDR的选举 伪节点 LSDB的更新 OSPF的配置 二、OSPF的高级特性 虚连接&#xff08;Virtual-Link&#xff09; OSPF的LSA和路由选择 OSPF…

C#读取本地图像的方法总结

前言&#xff1a; 大家好&#xff0c;我是上位机马工&#xff0c;硕士毕业4年年入40万&#xff0c;目前在一家自动化公司担任软件经理&#xff0c;从事C#上位机软件开发8年以上&#xff01;我们在C#开发C#程序的时候&#xff0c;有时候需要读取本地图像&#xff0c;下面进行详…

scrapy爬虫框架小案例

豆瓣案例 一、scrapy安装二、scrapy的基本使用&#xff08;爬虫项目创建->爬虫文件创建->运行 爬虫项目结构 response的属性和方法&#x1f31f;&#xff09;1、创建项目2、创建爬虫文件3、scrapy项目的结构4、运行爬虫文件5、response的属性和方法&#xff08;爬虫的处…

服务器实现ssh证书登录

1.生成公钥和私钥 ssh-keygen -t rsa 提示默认生成位置为/root/.ssh/id_rsa ,直接回车。(也可以自己修改) 提示输入证书的密码&#xff0c;可以留空&#xff0c;建议输入&#xff0c;如果输入了&#xff0c;则需要再次确认&#xff0c;记住这个证书密码&#xff08;证书再加…

css:转换

转换 移动 /* transform: translate(100px, 200px); */transform: translateX(100px);transform: translateY(100px); /*一个意思*/ 如果后面跟百分数的意思是移动盒子自身x/y方向长度的百分比&#xff0c;可以用作子绝父相控制盒子水平居中垂直居中 translate里的xy值是相对…