Python/短视频合成自动化/image.py

251 lines
8.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# -*- coding: utf-8 -*-
"""
生成图片模块
"""
# 列举导入模块
import cv2
import numpy
from pathlib import Path
import random
from typing import List, Dict, Optional, Union, Literal, Tuple
from uuid import uuid4
class Canvas:
"""
画布,用于绘制图片
"""
def __init__(
self,
materials_folder_path: str = r"E:\jianying\materials\纯图",
canvas_height: int = 1920,
canvas_width: int = 1080,
):
"""
初始化
:param materials_folder_path: 所有素材文件夹的根目录路径
:param canvas_height: 画布高度
:param canvas_width: 画布宽度
"""
# 构建所有素材文件夹的根目录路径
# self.materials_folder_path = Path(materials_folder_path)
self.materials_folder_path = Path(
"/Users/liubiren/Python/短视频合成自动化/纯图"
)
self.canvas_height, self.canvas_width = canvas_height, canvas_width
# 创建画布(默认尺寸为 1920x1080底色为白色
self.canvas = (
numpy.ones((self.canvas_height, self.canvas_width, 4), dtype=numpy.uint8)
* 255
)
def _get_materials_paths(self, folder_name: str) -> List[Path]:
"""
根据素材文件夹名称获取该文件夹内所有素材路径列表
:return: 该文件夹内所有素材路径列表
"""
# 构建指定素材文件路径
folder_path = self.materials_folder_path / folder_name
if not (folder_path.exists() and folder_path.is_dir()):
raise RuntimeError(f"{folder_name} 素材文件夹不存在")
# 获取该文件夹内所有素材路径列表
material_paths = list(folder_path.rglob(pattern="*.png"))
if not material_paths:
raise RuntimeError(f"{folder_name} 素材文件夹为空")
return material_paths
def _read_material(
self,
material_path: Path,
) -> numpy.ndarray:
"""
读取素材
:param material_path: 素素材文件路径
:return: 素材 OpenCV 数据
"""
with open(file=material_path, mode="rb") as file:
material_buf = numpy.frombuffer(
buffer=file.read(), dtype=numpy.uint8
) # 二进制数据
# 解码为图像 OpenCV 数据
material = cv2.imdecode(buf=material_buf, flags=cv2.IMREAD_UNCHANGED)
if material is None:
raise RuntimeError(f"读取素材失败")
return material
def _resize_material(
self,
material: numpy.ndarray,
scale: Union[Literal["fixed_width"], float] = 1.0,
) -> numpy.ndarray:
"""
缩放素材
:param material: 素材 OpenCV 数据
:param scale: 缩放比例
:return: 缩放后素材 OpenCV 数据
"""
# 获取素材高度和宽度
material_height, material_width = material.shape[:2]
match scale:
case "fixed_width":
material_height, material_width = (
int(material_height * self.canvas_width / material_width),
self.canvas_width,
)
case _:
material_height, material_width = int(material_height * scale), int(
material_width * scale
)
return cv2.resize(
src=material,
dsize=(material_width, material_height),
interpolation=cv2.INTER_LINEAR,
)
def _paste_material(
self,
material: numpy.ndarray,
position: Union[str, Tuple[Optional[int], Optional[int]]] = "center",
) -> None:
"""
粘贴素材
:param material: 素材 OpenCV 数据
:param position: 粘贴位置
:return: None
"""
# 获取素材高度和宽度
material_height, material_width = material.shape[:2]
# 计算素材在画布原点坐标
x = (self.canvas_width - material_width) // 2 # 水平方向坐标
y = (self.canvas_height - material_height) // 2 # 垂直方向坐标
match position:
case "center": # 居中
pass
case _:
if position[0] is not None:
x = position[0] - material_width // 2
if position[1] is not None:
y = position[1] - material_height // 2
# 计算素材左上角坐标
x1, y1 = max(x, 0), max(y, 0)
# 计算素材右下角坐标
x2, y2 = min(x + material_width, self.canvas_width), min(
y + material_height, self.canvas_height
)
# 截取素材有效区域
material = material[y1 - y : y2 - y, x1 - x : x2 - x]
# 取出画布对应区域
area = self.canvas[y1:y2, x1:x2]
# 透明度计算
alpha_fg = material[..., 3] / 255.0
alpha_bg = area[..., 3] / 255.0
# 颜色混合
for c in range(3):
area[..., c] = (
material[..., c] * alpha_fg + area[..., c] * alpha_bg * (1 - alpha_fg)
).astype(numpy.uint8)
# 新透明度
area[..., 3] = ((alpha_fg + alpha_bg * (1 - alpha_fg)) * 255).astype(
numpy.uint8
)
# 写回画布
self.canvas[y1:y2, x1:x2] = area
def _save(self) -> None:
"""
保存图片
:return: None
"""
# 构建图片路径
image_path = self.materials_folder_path / "成品" / f"{uuid4().hex.upper()}.jpg"
# 编码为二进制数据
success, image_encoded = cv2.imencode(
".jpg", img=self.canvas, params=[cv2.IMWRITE_JPEG_QUALITY, 95]
)
if success:
with open(file=(Path(__file__).parent / image_path), mode="wb") as file:
file.write(image_encoded.tobytes())
print("✅ 图片保存成功!")
else:
print("❌ 图片编码失败!")
def draw(self, folder_names: List[str], counts: int) -> None:
"""
绘制图片
:param folder_names: 绘制所用素材文件夹名称列表
:param counts: 绘制图片数量
:return: None
"""
materials_paths = {} # 绘制所用素材文件夹名称和其素材路径列表
for folder_name in folder_names:
materials_paths.update(
{folder_name: self._get_materials_paths(folder_name)}
)
current_count = 0 # 当前绘制图片数量计数
while current_count < counts:
self.canvas = (
numpy.ones(
(self.canvas_height, self.canvas_width, 4), dtype=numpy.uint8
)
* 255
)
# 竖纹素材
vertical_strip = self._read_material(
material_path=random.choice(materials_paths["竖纹"])
)
# 等宽缩放竖纹素材
vertical_strip = self._resize_material(
material=vertical_strip, scale="fixed_width"
)
# 粘贴竖纹素材到画布
self._paste_material(material=vertical_strip)
# 按钮素材
button = self._read_material(
material_path=random.choice(materials_paths["按钮"])
)
# 粘贴按钮素材到画布
self._paste_material(material=button)
# 箭头素材
arrow = self._read_material(
material_path=random.choice(materials_paths["箭头"])
)
# 粘贴箭头素材到画布
self._paste_material(material=arrow, position=(None, 1280))
# 声明素材
declaration = self._read_material(
material_path=random.choice(materials_paths["声明"])
)
# 粘贴声明素材到画布
self._paste_material(material=declaration, position=(None, 1640))
# 保存图片
self._save()
current_count += 1
canvas = Canvas()
canvas.draw(
folder_names=["竖纹", "按钮", "箭头", "声明"],
counts=500,
)