diff --git a/.gitignore b/.gitignore index ddecaff6..23ec49ac 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,14 @@ *.pyc **/__pycache__ .idea +.vscode/* .DS_Store hivision_modnet.onnx output/*.jpg # build outputs dist build +# checkpoint +*.pth +*.pt +*.onnx diff --git a/.vscode/settings.json b/.vscode/settings.json index 1bdbbc9b..5446da5d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -103,5 +103,6 @@ "**/__pycache__": true, ".idea": true }, - "python.testing.pytestEnabled": true + "python.testing.pytestEnabled": true, + "ros.distro": "humble" } diff --git a/app.py b/app.py index 24f7cb23..ba2f75ef 100644 --- a/app.py +++ b/app.py @@ -6,8 +6,8 @@ from src.layoutCreate import generate_layout_photo, generate_layout_image import pathlib import numpy as np -from image_utils import resize_image_to_kb -from data_utils import csv_to_size_list +from utils.image_utils import resize_image_to_kb +from utils.data_utils import csv_to_size_list import argparse diff --git a/deploy_api.py b/deploy_api.py index f4580818..f1d6bb35 100644 --- a/deploy_api.py +++ b/deploy_api.py @@ -1,40 +1,57 @@ from fastapi import FastAPI, UploadFile, Form +from fastapi.responses import FileResponse +import tempfile import onnxruntime from src.face_judgement_align import IDphotos_create from src.layoutCreate import generate_layout_photo, generate_layout_image from hivisionai.hycv.vision import add_background -import base64 +from utils import numpy_2_base64 import numpy as np import cv2 import ast +from utils import save_numpy_image +from loguru import logger app = FastAPI() -# 将图像转换为 Base64 编码 -def numpy_2_base64(img: np.ndarray): - retval, buffer = cv2.imencode(".png", img) - base64_image = base64.b64encode(buffer).decode("utf-8") +"""证件照制作接口 - return base64_image +input_image: 上传的图像文件 +size: 证件照尺寸,格式为字符串,如 '(413,295)' +hd_mode: 是否输出高清照片,默认为false + +""" -# 证件照智能制作接口 @app.post("/idphoto") async def idphoto_inference( input_image: UploadFile, size: str = Form(...), + hd_mode: bool = Form(False), head_measure_ratio=0.2, head_height_ratio=0.45, top_distance_max=0.12, top_distance_min=0.10, ): - image_bytes = await input_image.read() - nparr = np.frombuffer(image_bytes, np.uint8) - img = cv2.imdecode(nparr, cv2.IMREAD_COLOR) + try: + image_bytes = await input_image.read() + nparr = np.frombuffer(image_bytes, np.uint8) + img = cv2.imdecode(nparr, cv2.IMREAD_COLOR) + except Exception: + err_msg: str = "read image error" + logger.error(err_msg) + result_messgae = {"status": False, "err_msg": err_msg} + return result_messgae - # 将字符串转为元组 - size = ast.literal_eval(size) + try: + # 将字符串转为元组 + size = ast.literal_eval(size) + except Exception: + err_msg = "size param error, expect format like '(418,295)'" + logger.error(err_msg) + result_messgae = {"status": False, "err_msg": err_msg} + return result_messgae ( result_image_hd, @@ -63,46 +80,62 @@ async def idphoto_inference( # 如果检测到人脸数量不等于 1(照片无人脸 or 多人脸) if status == 0: result_messgae = {"status": False} - - # 如果检测到人脸数量等于 1, 则返回标准证和高清照结果(png 4 通道图像) + return result_messgae else: - result_messgae = { - "status": True, - "img_output_standard": numpy_2_base64(result_image_standard), - "img_output_standard_hd": numpy_2_base64(result_image_hd), - } - - return result_messgae + with tempfile.NamedTemporaryFile( + delete=False, suffix=".png" + ) as temp_standard, tempfile.NamedTemporaryFile( + delete=False, suffix=".png" + ) as temp_hd: + temp_standard_path = temp_standard.name + temp_hd_path = temp_hd.name + + # 假设 save_numpy_image 是一个将 numpy 图像保存为文件的函数 + save_numpy_image(result_image_standard, temp_standard_path) + save_numpy_image(result_image_hd, temp_hd_path) + + if hd_mode: + return FileResponse( + temp_hd_path, media_type="image/png", filename="output_hd.png" + ) + else: + return FileResponse( + temp_standard_path, + media_type="image/png", + filename="output_standard.png", + ) # 透明图像添加纯色背景接口 @app.post("/add_background") async def photo_add_background(input_image: UploadFile, color: str = Form(...)): - # 读取图像 - image_bytes = await input_image.read() - nparr = np.frombuffer(image_bytes, np.uint8) - img = cv2.imdecode(nparr, cv2.IMREAD_UNCHANGED) - - # 将字符串转为元组 - color = ast.literal_eval(color) - # 将元祖的 0 和 2 号数字交换 - color = (color[2], color[1], color[0]) - - # try: - result_messgae = { - "status": True, - "image": numpy_2_base64(add_background(img, bgr=color)), - } - - # except Exception as e: - # print(e) - # result_messgae = { - # "status": False, - # "error": e - # } + try: + image_bytes = await input_image.read() + nparr = np.frombuffer(image_bytes, np.uint8) + img = cv2.imdecode(nparr, cv2.IMREAD_UNCHANGED) + except Exception: + err_msg = "read image error" + logger.error(err_msg) + result_messgae = {"status": False, "err_msg": err_msg} + return result_messgae - return result_messgae + try: + # 将字符串转为元组 + color = ast.literal_eval(color) + # 将元组的 0 和 2 号数字交换 + color = (color[2], color[1], color[0]) + except Exception: + err_msg = "color param error, expect format like '(255,255,255)'" + logger.error(err_msg) + result_messgae = {"status": False, "err_msg": err_msg} + return result_messgae + + bg_img = add_background(img, bgr=color) + with tempfile.NamedTemporaryFile(delete=False, suffix=".jpg") as temp: + temp_path = temp.name + save_numpy_image(bg_img, temp_path) + return FileResponse(temp_path, media_type="image/jpg", filename="output.jpg") # 六寸排版照生成接口 @@ -112,9 +145,22 @@ async def generate_layout_photos(input_image: UploadFile, size: str = Form(...)) image_bytes = await input_image.read() nparr = np.frombuffer(image_bytes, np.uint8) img = cv2.imdecode(nparr, cv2.IMREAD_COLOR) + except Exception: + err_msg = "read image error" + logger.error(err_msg) + result_messgae = {"status": False, "err_msg": err_msg} + return result_messgae + try: size = ast.literal_eval(size) + except Exception: + err_msg = "size param error, expect format like '(418,295)'" + logger.error(err_msg) + result_messgae = {"status": False, "err_msg": err_msg} + return result_messgae + logger.info(f"size: {size}") + try: typography_arr, typography_rotate = generate_layout_photo( input_height=size[0], input_width=size[1] ) @@ -122,18 +168,16 @@ async def generate_layout_photos(input_image: UploadFile, size: str = Form(...)) result_layout_image = generate_layout_image( img, typography_arr, typography_rotate, height=size[0], width=size[1] ) - - result_messgae = { - "status": True, - "image": numpy_2_base64(result_layout_image), - } - except Exception as e: result_messgae = { "status": False, } + return result_messgae - return result_messgae + with tempfile.NamedTemporaryFile(delete=False, suffix=".jpg") as temp: + temp_path = temp.name + save_numpy_image(result_layout_image, temp_path) + return FileResponse(temp_path, media_type="image/jpg", filename="output.jpg") if __name__ == "__main__": diff --git a/hivisionai/hycv/vision.py b/hivisionai/hycv/vision.py index a5d58818..f6031675 100644 --- a/hivisionai/hycv/vision.py +++ b/hivisionai/hycv/vision.py @@ -4,18 +4,24 @@ import functools import time + def calTime(mark): """ 一个输出函数时间的装饰器。 :param mark: str, 可选填,如果填了就会在 print 开头加上 mark 标签。 """ if isinstance(mark, str): + def decorater(func): @functools.wraps(func) def wrapper(*args, **kw): start_time = time.time() return_param = func(*args, **kw) - print("[Mark-{}] {} 函数花费的时间为 {:.2f}.".format(mark, func.__name__, time.time() - start_time)) + print( + "[Mark-{}] {} 函数花费的时间为 {:.2f}.".format( + mark, func.__name__, time.time() - start_time + ) + ) return return_param return wrapper @@ -28,7 +34,11 @@ def wrapper(*args, **kw): def wrapper(*args, **kw): start_time = time.time() return_param = func(*args, **kw) - print("{} 函数花费的时间为 {:.2f}.".format(func.__name__, time.time() - start_time)) + print( + "{} 函数花费的时间为 {:.2f}.".format( + func.__name__, time.time() - start_time + ) + ) return return_param return wrapper @@ -44,7 +54,9 @@ def ChangeImageDPI(input_path, output_path, dpi=300): image = Image.open(input_path) image.save(output_path, dpi=(dpi, dpi)) # print(1) - print("Your Image's DPI have been changed. The last DPI = ({},{}) ".format(dpi,dpi)) + print( + "Your Image's DPI have been changed. The last DPI = ({},{}) ".format(dpi, dpi) + ) def IDphotos_cut(x1, y1, x2, y2, img): @@ -63,7 +75,7 @@ def IDphotos_cut(x1, y1, x2, y2, img): ------------------------------------ """ - crop_size = (y2-y1, x2-x1) + crop_size = (y2 - y1, x2 - x1) """ ------------------------------------ temp_x_1:裁剪框左边超出图像部分 @@ -97,9 +109,13 @@ def IDphotos_cut(x1, y1, x2, y2, img): print("crop_size:", crop_size) background_bgr = np.full((crop_size[0], crop_size[1]), 255, dtype=np.uint8) background_a = np.full((crop_size[0], crop_size[1]), 0, dtype=np.uint8) - background = cv2.merge((background_bgr, background_bgr, background_bgr, background_a)) + background = cv2.merge( + (background_bgr, background_bgr, background_bgr, background_a) + ) - background[temp_y_1: crop_size[0] - temp_y_2, temp_x_1: crop_size[1] - temp_x_2] = img[y1:y2, x1:x2] + background[ + temp_y_1 : crop_size[0] - temp_y_2, temp_x_1 : crop_size[1] - temp_x_2 + ] = img[y1:y2, x1:x2] return background @@ -126,7 +142,9 @@ def resize_image_esp(input_image, esp=2000): width = int((esp / length) * width) length = esp print(length, width) - im_resize = cv2.resize(input_image, (length, width), interpolation=cv2.INTER_AREA) + im_resize = cv2.resize( + input_image, (length, width), interpolation=cv2.INTER_AREA + ) return im_resize else: return input_image @@ -149,7 +167,12 @@ def resize_image_by_min(input_image, esp=600): new_height = esp new_width = width * esp // height - return cv2.resize(input_image, (new_width, new_height), interpolation=cv2.INTER_AREA), new_height / height + return ( + cv2.resize( + input_image, (new_width, new_height), interpolation=cv2.INTER_AREA + ), + new_height / height, + ) else: return input_image, 1 @@ -209,13 +232,13 @@ def draw_picture_rectangle(image, bbox, pen_size=2, pen_color=(0, 0, 255)): y1 = int(bbox[1]) x2 = int(bbox[2]) y2 = int(bbox[3]) - cv2.rectangle(image, (x1,y1), (x2, y2), pen_color, pen_size) + cv2.rectangle(image, (x1, y1), (x2, y2), pen_color, pen_size) return image def generate_gradient(start_color, width, height, mode="updown"): # 定义背景颜色 - end_color = (255, 255, 255) # 白色 + end_color = (255, 255, 255) # 白色 # 创建一个空白图像 r_out = np.zeros((height, width), dtype=int) @@ -225,9 +248,15 @@ def generate_gradient(start_color, width, height, mode="updown"): if mode == "updown": # 生成上下渐变色 for y in range(height): - r = int((y / height) * end_color[0] + ((height - y) / height) * start_color[0]) - g = int((y / height) * end_color[1] + ((height - y) / height) * start_color[1]) - b = int((y / height) * end_color[2] + ((height - y) / height) * start_color[2]) + r = int( + (y / height) * end_color[0] + ((height - y) / height) * start_color[0] + ) + g = int( + (y / height) * end_color[1] + ((height - y) / height) * start_color[1] + ) + b = int( + (y / height) * end_color[2] + ((height - y) / height) * start_color[2] + ) r_out[y, :] = r g_out[y, :] = g b_out[y, :] = b @@ -236,16 +265,25 @@ def generate_gradient(start_color, width, height, mode="updown"): # 生成中心渐变色 img = np.zeros((height, width, 3)) # 定义椭圆中心和半径 - center = (width//2, height//2) + center = (width // 2, height // 2) end_axies = max(height, width) # 定义渐变色 end_color = (255, 255, 255) # 绘制椭圆 for y in range(end_axies): axes = (end_axies - y, end_axies - y) - r = int((y / end_axies) * end_color[0] + ((end_axies - y) / end_axies) * start_color[0]) - g = int((y / end_axies) * end_color[1] + ((end_axies - y) / end_axies) * start_color[1]) - b = int((y / end_axies) * end_color[2] + ((end_axies - y) / end_axies) * start_color[2]) + r = int( + (y / end_axies) * end_color[0] + + ((end_axies - y) / end_axies) * start_color[0] + ) + g = int( + (y / end_axies) * end_color[1] + + ((end_axies - y) / end_axies) * start_color[1] + ) + b = int( + (y / end_axies) * end_color[2] + + ((end_axies - y) / end_axies) * start_color[2] + ) cv2.ellipse(img, center, axes, 0, 0, 360, (b, g, r), -1) b_out, g_out, r_out = cv2.split(np.uint64(img)) @@ -274,7 +312,9 @@ def add_background(input_image, bgr=(0, 0, 0), mode="pure_color"): else: b2, g2, r2 = generate_gradient(bgr, width, height, mode="center") - output = cv2.merge(((b - b2) * a_cal + b2, (g - g2) * a_cal + g2, (r - r2) * a_cal + r2)) + output = cv2.merge( + ((b - b2) * a_cal + b2, (g - g2) * a_cal + g2, (r - r2) * a_cal + r2) + ) return output @@ -338,11 +378,25 @@ def cover_image(image, background, x, y, mode=1): wuqiong_img_y = height2 + 1 wuqiong_img_x = width2 + 1 - def cover_mode(image, background, imgy1=0, imgy2=-1, imgx1=0, imgx2=-1, bgy1=0, bgy2=-1, bgx1=0, bgx2=-1, mode=1): + def cover_mode( + image, + background, + imgy1=0, + imgy2=-1, + imgx1=0, + imgx2=-1, + bgy1=0, + bgy2=-1, + bgx1=0, + bgx2=-1, + mode=1, + ): if mode == 1: background[bgy1:bgy2, bgx1:bgx2] = image[imgy1:imgy2, imgx1:imgx2] elif mode == 2: - background[bgy1:bgy2, bgx1:bgx2] = cv2.add(background[bgy1:bgy2, bgx1:bgx2], image[imgy1:imgy2, imgx1:imgx2]) + background[bgy1:bgy2, bgx1:bgx2] = cv2.add( + background[bgy1:bgy2, bgx1:bgx2], image[imgy1:imgy2, imgx1:imgx2] + ) elif mode == 3: b, g, r, a = cv2.split(image[imgy1:imgy2, imgx1:imgx2]) b2, g2, r2, a2 = cv2.split(background[bgy1:bgy2, bgx1:bgx2]) @@ -358,18 +412,66 @@ def cover_mode(image, background, imgy1=0, imgy2=-1, imgx1=0, imgx2=-1, bgy1=0, y2 = y + height2 if x2 <= width1 and y2 <= height1: - background = cover_mode(image, background,0,wuqiong_img_y,0,wuqiong_img_x,y,y2,x,x2,mode) + background = cover_mode( + image, + background, + 0, + wuqiong_img_y, + 0, + wuqiong_img_x, + y, + y2, + x, + x2, + mode, + ) elif x2 > width1 and y2 <= height1: # background[y:y2, x:] = image[:, :width1 - x] - background = cover_mode(image, background, 0, wuqiong_img_y, 0, width1-x, y, y2, x, wuqiong_bg_x,mode) + background = cover_mode( + image, + background, + 0, + wuqiong_img_y, + 0, + width1 - x, + y, + y2, + x, + wuqiong_bg_x, + mode, + ) elif x2 <= width1 and y2 > height1: # background[y:, x:x2] = image[:height1 - y, :] - background = cover_mode(image, background, 0, height1-y, 0, wuqiong_img_x, y, wuqiong_bg_y, x, x2,mode) + background = cover_mode( + image, + background, + 0, + height1 - y, + 0, + wuqiong_img_x, + y, + wuqiong_bg_y, + x, + x2, + mode, + ) else: # background[y:, x:] = image[:height1 - y, :width1 - x] - background = cover_mode(image, background, 0, height1-y, 0, width1-x, y, wuqiong_bg_y, x, wuqiong_bg_x,mode) + background = cover_mode( + image, + background, + 0, + height1 - y, + 0, + width1 - x, + y, + wuqiong_bg_y, + x, + wuqiong_bg_x, + mode, + ) elif x < 0 and y >= 0: x2 = x + width2 @@ -377,16 +479,64 @@ def cover_mode(image, background, imgy1=0, imgy2=-1, imgx1=0, imgx2=-1, bgy1=0, if x2 <= width1 and y2 <= height1: # background[y:y2, :x + width2] = image[:, abs(x):] - background = cover_mode(image, background, 0, wuqiong_img_y, abs(x), wuqiong_img_x, y, y2, 0, x+width2,mode) + background = cover_mode( + image, + background, + 0, + wuqiong_img_y, + abs(x), + wuqiong_img_x, + y, + y2, + 0, + x + width2, + mode, + ) elif x2 > width1 and y2 <= height1: - background = cover_mode(image, background, 0, wuqiong_img_y, abs(x), width1+abs(x), y, y2, 0, wuqiong_bg_x,mode) + background = cover_mode( + image, + background, + 0, + wuqiong_img_y, + abs(x), + width1 + abs(x), + y, + y2, + 0, + wuqiong_bg_x, + mode, + ) elif x2 <= 0: pass elif x2 <= width1 and y2 > height1: - background = cover_mode(image, background, 0, height1-y, abs(x), wuqiong_img_x, y, wuqiong_bg_y, 0, x2, mode) + background = cover_mode( + image, + background, + 0, + height1 - y, + abs(x), + wuqiong_img_x, + y, + wuqiong_bg_y, + 0, + x2, + mode, + ) else: # background[y:, :] = image[:height1 - y, abs(x):width1 + abs(x)] - background = cover_mode(image, background, 0, height1-y, abs(x), width1+abs(x), y, wuqiong_bg_y, 0, wuqiong_bg_x,mode) + background = cover_mode( + image, + background, + 0, + height1 - y, + abs(x), + width1 + abs(x), + y, + wuqiong_bg_y, + 0, + wuqiong_bg_x, + mode, + ) elif x >= 0 and y < 0: x2 = x + width2 @@ -395,16 +545,64 @@ def cover_mode(image, background, imgy1=0, imgy2=-1, imgx1=0, imgx2=-1, bgy1=0, pass if x2 <= width1 and y2 <= height1: # background[:y2, x:x2] = image[abs(y):, :] - background = cover_mode(image, background, abs(y), wuqiong_img_y, 0, wuqiong_img_x, 0, y2, x, x2,mode) + background = cover_mode( + image, + background, + abs(y), + wuqiong_img_y, + 0, + wuqiong_img_x, + 0, + y2, + x, + x2, + mode, + ) elif x2 > width1 and y2 <= height1: # background[:y2, x:] = image[abs(y):, :width1 - x] - background = cover_mode(image, background, abs(y), wuqiong_img_y, 0, width1-x, 0, y2, x, wuqiong_bg_x,mode) + background = cover_mode( + image, + background, + abs(y), + wuqiong_img_y, + 0, + width1 - x, + 0, + y2, + x, + wuqiong_bg_x, + mode, + ) elif x2 <= width1 and y2 > height1: # background[:, x:x2] = image[abs(y):height1 + abs(y), :] - background = cover_mode(image, background, abs(y), height1+abs(y), 0, wuqiong_img_x, 0, wuqiong_bg_y, x, x2,mode) + background = cover_mode( + image, + background, + abs(y), + height1 + abs(y), + 0, + wuqiong_img_x, + 0, + wuqiong_bg_y, + x, + x2, + mode, + ) else: # background[:, x:] = image[abs(y):height1 + abs(y), :width1 - abs(x)] - background = cover_mode(image, background, abs(y), height1+abs(y), 0, width1-abs(x), 0, wuqiong_bg_x, x, wuqiong_bg_x,mode) + background = cover_mode( + image, + background, + abs(y), + height1 + abs(y), + 0, + width1 - abs(x), + 0, + wuqiong_bg_x, + x, + wuqiong_bg_x, + mode, + ) else: x2 = x + width2 @@ -413,16 +611,64 @@ def cover_mode(image, background, imgy1=0, imgy2=-1, imgx1=0, imgx2=-1, bgy1=0, pass if x2 <= width1 and y2 <= height1: # background[:y2, :x2] = image[abs(y):, abs(x):] - background = cover_mode(image, background, abs(y), wuqiong_img_y, abs(x), wuqiong_img_x, 0, y2, 0, x2,mode) + background = cover_mode( + image, + background, + abs(y), + wuqiong_img_y, + abs(x), + wuqiong_img_x, + 0, + y2, + 0, + x2, + mode, + ) elif x2 > width1 and y2 <= height1: # background[:y2, :] = image[abs(y):, abs(x):width1 + abs(x)] - background = cover_mode(image, background, abs(y), wuqiong_img_y, abs(x), width1+abs(x), 0, y2, 0, wuqiong_bg_x,mode) + background = cover_mode( + image, + background, + abs(y), + wuqiong_img_y, + abs(x), + width1 + abs(x), + 0, + y2, + 0, + wuqiong_bg_x, + mode, + ) elif x2 <= width1 and y2 > height1: # background[:, :x2] = image[abs(y):height1 + abs(y), abs(x):] - background = cover_mode(image, background, abs(y), height1+abs(y), abs(x), wuqiong_img_x, 0, wuqiong_bg_y, 0, x2,mode) + background = cover_mode( + image, + background, + abs(y), + height1 + abs(y), + abs(x), + wuqiong_img_x, + 0, + wuqiong_bg_y, + 0, + x2, + mode, + ) else: # background[:, :] = image[abs(y):height1 - abs(y), abs(x):width1 + abs(x)] - background = cover_mode(image, background, abs(y), height1-abs(y), abs(x), width1+abs(x), 0, wuqiong_bg_y, 0, wuqiong_bg_x,mode) + background = cover_mode( + image, + background, + abs(y), + height1 - abs(y), + abs(x), + width1 + abs(x), + 0, + wuqiong_bg_y, + 0, + wuqiong_bg_x, + mode, + ) return background @@ -443,4 +689,4 @@ def image2bgr(input_image): if __name__ == "__main__": image = cv2.imread("./03.png", -1) result_image = add_background(image, bgr=(255, 255, 255)) - cv2.imwrite("test.jpg", result_image) \ No newline at end of file + cv2.imwrite("test.jpg", result_image) diff --git a/inference.py b/inference.py index 10db41b0..5e950360 100644 --- a/inference.py +++ b/inference.py @@ -4,7 +4,7 @@ import argparse import numpy as np import onnxruntime -from image_utils import resize_image_to_kb +from utils import resize_image_to_kb from src.face_judgement_align import IDphotos_create from hivisionai.hycv.vision import add_background from src.layoutCreate import generate_layout_photo, generate_layout_image diff --git a/requests_api.py b/requests_api.py index 36912402..239e139d 100644 --- a/requests_api.py +++ b/requests_api.py @@ -4,7 +4,7 @@ from io import BytesIO import argparse import os -from image_utils import resize_image_to_kb +from utils.image_utils import resize_image_to_kb def base64_save(base64_image_data, save_path, kb=None): diff --git a/utils/__init__.py b/utils/__init__.py new file mode 100644 index 00000000..6ad25128 --- /dev/null +++ b/utils/__init__.py @@ -0,0 +1,2 @@ +from .data_utils import * +from .image_utils import * diff --git a/data_utils.py b/utils/data_utils.py similarity index 100% rename from data_utils.py rename to utils/data_utils.py diff --git a/image_utils.py b/utils/image_utils.py similarity index 69% rename from image_utils.py rename to utils/image_utils.py index 4b29aeb7..1a8eed68 100644 --- a/image_utils.py +++ b/utils/image_utils.py @@ -1,6 +1,8 @@ from PIL import Image import io import numpy as np +import cv2 +import base64 def resize_image_to_kb(input_image, output_image_path, target_size_kb): @@ -61,3 +63,34 @@ def resize_image_to_kb(input_image, output_image_path, target_size_kb): # Ensure quality does not go below 1 if quality < 1: quality = 1 + + +def numpy_2_base64(img: np.ndarray): + retval, buffer = cv2.imencode(".png", img) + base64_image = base64.b64encode(buffer).decode("utf-8") + + return base64_image + + +def save_numpy_image(numpy_img, file_path): + # 检查数组的形状 + if numpy_img.shape[2] == 4: + # 将 BGR 转换为 RGB,并保留透明通道 + rgb_img = np.concatenate( + (np.flip(numpy_img[:, :, :3], axis=-1), numpy_img[:, :, 3:]), axis=-1 + ).astype(np.uint8) + img = Image.fromarray(rgb_img, mode="RGBA") + else: + # 将 BGR 转换为 RGB + rgb_img = np.flip(numpy_img, axis=-1).astype(np.uint8) + img = Image.fromarray(rgb_img, mode="RGB") + + img.save(file_path) + + +def numpy_to_bytes(numpy_img): + img = Image.fromarray(numpy_img) + img_byte_arr = io.BytesIO() + img.save(img_byte_arr, format="PNG") + img_byte_arr.seek(0) + return img_byte_arr