Add 1024 face crop tool
This commit is contained in:
1 parent
195a3406e0
commit
6bc251465b
1 file changed
+203
@@ -0,0 +1,203 @@
|
|||||||
|
import cv2
|
||||||
|
import os
|
||||||
|
import argparse
|
||||||
|
import numpy as np
|
||||||
|
from ultralytics import YOLO
|
||||||
|
|
||||||
|
# ===================== 模型路径配置 =====================
|
||||||
|
# 请确保此路径下有模型文件,如果没有会自动下载
|
||||||
|
YOLO_FACE_MODEL_PATH = "/root/code/lenovo_qinzhe_ssh/sam3/sam3_new/face/yolov8n-face.pt"
|
||||||
|
# =======================================================
|
||||||
|
|
||||||
|
# 加载YOLO人脸检测模型
|
||||||
|
yolo_model = YOLO(YOLO_FACE_MODEL_PATH)
|
||||||
|
|
||||||
|
# 支持的图片格式(包含大小写)
|
||||||
|
SUPPORTED_FORMATS = {
|
||||||
|
'jpg': 'jpg',
|
||||||
|
'jpeg': 'jpeg',
|
||||||
|
'png': 'png',
|
||||||
|
'bmp': 'bmp',
|
||||||
|
'JPG': 'jpg',
|
||||||
|
'JPEG': 'jpeg',
|
||||||
|
'PNG': 'png',
|
||||||
|
'BMP': 'bmp'
|
||||||
|
}
|
||||||
|
|
||||||
|
def detect_face_bbox(image, conf_threshold=0.5):
|
||||||
|
"""
|
||||||
|
使用YOLO检测人脸边界框
|
||||||
|
:param image: 输入BGR图像
|
||||||
|
:param conf_threshold: 置信度阈值
|
||||||
|
:return: 人脸边界框列表 [(x1, y1, x2, y2), ...],若无检测结果返回空列表
|
||||||
|
"""
|
||||||
|
results = yolo_model(image, conf=conf_threshold)
|
||||||
|
bboxes = []
|
||||||
|
if results and len(results) > 0:
|
||||||
|
for box in results[0].boxes:
|
||||||
|
if box.conf[0] >= conf_threshold:
|
||||||
|
x1, y1, x2, y2 = map(int, box.xyxy[0])
|
||||||
|
bboxes.append((x1, y1, x2, y2))
|
||||||
|
return bboxes
|
||||||
|
|
||||||
|
def is_supported_image(file_path):
|
||||||
|
"""
|
||||||
|
检查文件是否为支持的图片格式
|
||||||
|
"""
|
||||||
|
if not os.path.isfile(file_path):
|
||||||
|
return False
|
||||||
|
file_ext = os.path.splitext(file_path)[1].lstrip('.')
|
||||||
|
return file_ext in SUPPORTED_FORMATS
|
||||||
|
|
||||||
|
def get_output_path(input_img_path, output_arg):
|
||||||
|
"""
|
||||||
|
根据输出参数确定最终的输出路径
|
||||||
|
:param input_img_path: 输入图片路径
|
||||||
|
:param output_arg: 命令行传入的-o参数(目录或文件路径)
|
||||||
|
:return: 最终的输出图片路径
|
||||||
|
"""
|
||||||
|
# 如果输出参数是目录
|
||||||
|
if os.path.isdir(output_arg):
|
||||||
|
img_name = os.path.basename(input_img_path)
|
||||||
|
return os.path.join(output_arg, img_name)
|
||||||
|
|
||||||
|
# 如果输出参数是文件路径(且目录存在)
|
||||||
|
output_dir = os.path.dirname(output_arg)
|
||||||
|
if output_dir and not os.path.exists(output_dir):
|
||||||
|
os.makedirs(output_dir, exist_ok=True)
|
||||||
|
|
||||||
|
# 检查输出文件格式是否支持
|
||||||
|
output_ext = os.path.splitext(output_arg)[1].lstrip('.')
|
||||||
|
if output_ext not in SUPPORTED_FORMATS:
|
||||||
|
# 使用输入文件的格式
|
||||||
|
input_ext = os.path.splitext(input_img_path)[1].lstrip('.')
|
||||||
|
input_format = SUPPORTED_FORMATS.get(input_ext, 'jpg')
|
||||||
|
output_arg = f"{os.path.splitext(output_arg)[0]}.{input_format}"
|
||||||
|
|
||||||
|
return output_arg
|
||||||
|
|
||||||
|
def crop_face_center_1024(input_path, output_arg, conf_threshold=0.5):
|
||||||
|
"""
|
||||||
|
以检测到的人脸为中心裁剪出1024×1024的图片
|
||||||
|
:param input_path: 输入图片文件路径
|
||||||
|
:param output_arg: 输出图片路径或目录路径
|
||||||
|
:param conf_threshold: YOLO人脸检测置信度阈值
|
||||||
|
:return: 裁剪是否成功
|
||||||
|
"""
|
||||||
|
# 读取图片(确保以彩色模式读取)
|
||||||
|
image = cv2.imread(input_path, cv2.IMREAD_COLOR)
|
||||||
|
if image is None:
|
||||||
|
print(f"❌ 无法读取图片:{input_path}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
h, w = image.shape[:2]
|
||||||
|
|
||||||
|
# ========== YOLO人脸检测 ==========
|
||||||
|
face_bboxes = detect_face_bbox(image, conf_threshold)
|
||||||
|
if not face_bboxes:
|
||||||
|
print(f"⚠️ 未检测到人脸:{input_path}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 取第一个检测到的人脸
|
||||||
|
x1, y1, x2, y2 = face_bboxes[0]
|
||||||
|
|
||||||
|
# 计算人脸中心坐标
|
||||||
|
face_center_x = (x1 + x2) // 2
|
||||||
|
face_center_y = (y1 + y2) // 2
|
||||||
|
|
||||||
|
# 计算1024×1024裁剪区域的边界(以人脸为中心)
|
||||||
|
crop_size = 1024
|
||||||
|
half_size = crop_size // 2
|
||||||
|
|
||||||
|
# 计算裁剪区域的左上角和右下角坐标
|
||||||
|
crop_x1 = face_center_x - half_size
|
||||||
|
crop_y1 = face_center_y - half_size
|
||||||
|
crop_x2 = crop_x1 + crop_size
|
||||||
|
crop_y2 = crop_y1 + crop_size
|
||||||
|
|
||||||
|
# 处理边界越界问题(如果裁剪区域超出图片范围)
|
||||||
|
pad_left = max(0, -crop_x1)
|
||||||
|
pad_top = max(0, -crop_y1)
|
||||||
|
pad_right = max(0, crop_x2 - w)
|
||||||
|
pad_bottom = max(0, crop_y2 - h)
|
||||||
|
|
||||||
|
# 调整裁剪坐标到图片范围内
|
||||||
|
crop_x1_clamped = max(0, crop_x1)
|
||||||
|
crop_y1_clamped = max(0, crop_y1)
|
||||||
|
crop_x2_clamped = min(w, crop_x2)
|
||||||
|
crop_y2_clamped = min(h, crop_y2)
|
||||||
|
|
||||||
|
# 裁剪图片
|
||||||
|
cropped_image = image[crop_y1_clamped:crop_y2_clamped, crop_x1_clamped:crop_x2_clamped]
|
||||||
|
|
||||||
|
# 如果有越界,用黑色填充(保持图片维度)
|
||||||
|
if pad_left > 0 or pad_top > 0 or pad_right > 0 or pad_bottom > 0:
|
||||||
|
cropped_image = cv2.copyMakeBorder(
|
||||||
|
cropped_image,
|
||||||
|
pad_top, pad_bottom, pad_left, pad_right,
|
||||||
|
cv2.BORDER_CONSTANT,
|
||||||
|
value=[0, 0, 0] # 黑色填充(BGR格式)
|
||||||
|
)
|
||||||
|
|
||||||
|
# 强制调整为1024×1024(确保输出尺寸准确)
|
||||||
|
cropped_image = cv2.resize(cropped_image, (1024, 1024), interpolation=cv2.INTER_AREA)
|
||||||
|
|
||||||
|
# 确定最终输出路径
|
||||||
|
output_path = get_output_path(input_path, output_arg)
|
||||||
|
|
||||||
|
# 保存图片(根据格式选择合适的编码)
|
||||||
|
file_ext = os.path.splitext(output_path)[1].lstrip('.')
|
||||||
|
file_format = SUPPORTED_FORMATS.get(file_ext, 'jpg')
|
||||||
|
|
||||||
|
save_success = False
|
||||||
|
if file_format == 'jpg' or file_format == 'jpeg':
|
||||||
|
# JPG格式:95%质量保存
|
||||||
|
save_success = cv2.imwrite(output_path, cropped_image, [int(cv2.IMWRITE_JPEG_QUALITY), 95])
|
||||||
|
elif file_format == 'png':
|
||||||
|
# PNG格式:无损压缩
|
||||||
|
save_success = cv2.imwrite(output_path, cropped_image, [int(cv2.IMWRITE_PNG_COMPRESSION), 0])
|
||||||
|
else:
|
||||||
|
# 其他格式默认保存
|
||||||
|
save_success = cv2.imwrite(output_path, cropped_image)
|
||||||
|
|
||||||
|
if save_success:
|
||||||
|
print(f"✅ 裁剪完成:{output_path} (尺寸:1024×1024,人脸中心:({face_center_x}, {face_center_y}))")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
print(f"❌ 保存图片失败:{output_path}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# 创建参数解析器
|
||||||
|
parser = argparse.ArgumentParser(description='以人脸为中心裁剪1024×1024图片的工具')
|
||||||
|
|
||||||
|
# 添加命令行参数
|
||||||
|
parser.add_argument('-i', '--input', required=True,
|
||||||
|
help='输入图片文件路径(仅支持单张图片,格式:jpg/jpeg/png/bmp)')
|
||||||
|
parser.add_argument('-o', '--output', required=True,
|
||||||
|
help='输出图片路径(如:/path/to/output.jpg)或输出目录路径(如:/path/to/output/)')
|
||||||
|
parser.add_argument('-c', '--conf', type=float, default=0.5,
|
||||||
|
help='人脸检测置信度阈值(默认0.5,值越高检测越严格)')
|
||||||
|
|
||||||
|
# 解析参数
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# 验证输入文件
|
||||||
|
if not os.path.exists(args.input):
|
||||||
|
print(f"❌ 错误:输入文件不存在:{args.input}")
|
||||||
|
return
|
||||||
|
|
||||||
|
if not is_supported_image(args.input):
|
||||||
|
print(f"❌ 错误:输入文件不是支持的图片格式(支持:{', '.join(set(SUPPORTED_FORMATS.values()))})")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 执行裁剪
|
||||||
|
success = crop_face_center_1024(args.input, args.output, args.conf)
|
||||||
|
|
||||||
|
if success:
|
||||||
|
print("\n🎉 图片裁剪任务完成!")
|
||||||
|
else:
|
||||||
|
print("\n❌ 图片裁剪任务失败!")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in new issue
Block a user