305 lines
10 KiB
Python
305 lines
10 KiB
Python
import cv2
|
||
import numpy as np
|
||
import itertools
|
||
import os
|
||
import time
|
||
import math
|
||
|
||
from PIL import Image
|
||
|
||
# 设置输入输出文件夹路径
|
||
input_folder = "output10" # 输入文件夹
|
||
output_folder = "output12" # 输出文件夹
|
||
|
||
# 创建输出文件夹(如果不存在)
|
||
os.makedirs(output_folder, exist_ok=True)
|
||
start_time = time.time()
|
||
|
||
import numpy as np
|
||
|
||
def order_points(points, clockwise=True):
|
||
"""
|
||
将四边形的四个点按顺时针或逆时针顺序排列。
|
||
:param points: 四边形的四个点 [(x1, y1), (x2, y2), (x3, y3), (x4, y4)]
|
||
:param clockwise: 是否按顺时针排列,默认 True(顺时针),False(逆时针)
|
||
:return: 排序后的点
|
||
"""
|
||
# 计算质心
|
||
center = np.mean(points, axis=0)
|
||
|
||
# 计算每个点相对于质心的角度
|
||
angles = [np.arctan2(point[1] - center[1], point[0] - center[0]) for point in points]
|
||
|
||
# 按角度排序,顺时针(从大到小)或逆时针(从小到大)
|
||
sorted_points = [point for _, point in sorted(zip(angles, points), reverse=clockwise)]
|
||
return sorted_points
|
||
|
||
|
||
def counter_order(contours):
|
||
for contour in contours:
|
||
# 计算轮廓的签名面积
|
||
area = cv2.contourArea(contour, oriented=True)
|
||
# 如果面积是负值,说明轮廓是逆时针
|
||
return area > 0
|
||
|
||
def calculate_weight(points, non_transparent_area):
|
||
contour = np.array(points, dtype=np.int32)
|
||
area = cv2.contourArea(contour)
|
||
|
||
if area / non_transparent_area < 0.8:
|
||
return -1
|
||
|
||
edges = [np.linalg.norm(np.array(points[i]) - np.array(points[(i + 1) % 4])) for i in range(4)]
|
||
max_edge, min_edge = max(edges), min(edges)
|
||
|
||
if max_edge - min_edge > max_edge / 7:
|
||
return -1
|
||
|
||
edge_similarity = 1 - (max_edge - min_edge) / max_edge
|
||
|
||
angles = []
|
||
for i in range(4):
|
||
v1 = np.array(points[(i + 1) % 4]) - np.array(points[i])
|
||
v2 = np.array(points[(i + 3) % 4]) - np.array(points[i])
|
||
cosine_angle = np.dot(v1, v2) / (np.linalg.norm(v1) * np.linalg.norm(v2))
|
||
angle = np.degrees(np.arccos(np.clip(cosine_angle, -1.0, 1.0)))
|
||
angles.append(angle)
|
||
|
||
if any(angle < 80 or angle > 100 for angle in angles):
|
||
return -1
|
||
|
||
angle_similarity = 1 - sum(abs(angle - 90) for angle in angles) / 360
|
||
square_similarity = edge_similarity * angle_similarity
|
||
return square_similarity * 0.4 + (area / non_transparent_area) * 0.6
|
||
|
||
def find_best_quadrilateral(corner_points, non_transparent_area):
|
||
quadrilaterals = list(itertools.combinations(corner_points, 4))
|
||
best_quad = None
|
||
max_weight = -1
|
||
|
||
for quad in quadrilaterals:
|
||
quad = order_points(quad)
|
||
weight = calculate_weight(quad, non_transparent_area)
|
||
if weight > max_weight:
|
||
max_weight = weight
|
||
best_quad = quad
|
||
|
||
return best_quad, max_weight
|
||
|
||
def extract_and_save_pixels(image , coordinates, out_image_file):
|
||
# 创建一个空白图像,大小为原图大小
|
||
transparent_img = np.zeros_like(image)
|
||
|
||
# 设置指定坐标的像素值
|
||
for x, y in coordinates:
|
||
if 0 <= x < image.shape[1] and 0 <= y < image.shape[0]: # 检查是否在范围内
|
||
transparent_img[y, x] = image[y, x]
|
||
|
||
|
||
|
||
|
||
|
||
# 获取开始坐标和结束坐标
|
||
start_point = coordinates[0]
|
||
end_point = coordinates[-1]
|
||
|
||
# 计算连线的中间点
|
||
middle_point = (
|
||
(start_point[0] + end_point[0]) // 2,
|
||
(start_point[1] + end_point[1]) // 2,
|
||
)
|
||
|
||
# 检查中间点是否在图像范围内
|
||
if not (0 <= middle_point[0] < image.shape[1] and 0 <= middle_point[1] < image.shape[0]):
|
||
raise ValueError(f"中间点 {middle_point} 超出了图像边界。")
|
||
|
||
# 提取中间点的 alpha 通道值
|
||
alpha_value = image[middle_point[1], middle_point[0], 3] # alpha 通道在第 4 位
|
||
|
||
# 判断透明性
|
||
is_transparent = alpha_value == 0
|
||
print(f"中间点 {middle_point} 的透明状态: {'透明' if is_transparent else '非透明'}")
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
# 转换坐标为 NumPy 数组
|
||
coordinates_array = np.array(coordinates)
|
||
|
||
# 计算最小外接矩形
|
||
rect = cv2.minAreaRect(coordinates_array)
|
||
size = tuple(map(int, rect[1]))
|
||
|
||
# 如果宽度或高度小于5像素,终止方法
|
||
if size[0] < 5 or size[1] < 5:
|
||
return
|
||
|
||
# 扩展矩形尺寸
|
||
expanded_width = size[0] + 4 # 每侧扩展 2 个像素
|
||
expanded_height = size[1] + 4
|
||
expanded_size = (expanded_width, expanded_height)
|
||
|
||
|
||
# 获取旋转矩阵并旋转图像
|
||
angle = rect[-1]
|
||
if angle < -45:
|
||
angle += 90
|
||
center = tuple(map(int, rect[0]))
|
||
size = tuple(map(int, rect[1]))
|
||
rotation_matrix = cv2.getRotationMatrix2D(center, angle, 1.0)
|
||
# 对图像进行仿射变换
|
||
rotated_img = cv2.warpAffine(transparent_img, rotation_matrix, (image.shape[1], image.shape[0]),
|
||
flags=cv2.INTER_LINEAR, borderMode=cv2.BORDER_CONSTANT, borderValue=(0, 0, 0, 0))
|
||
|
||
# 确保尺寸为正数
|
||
size = (max(int(size[0]), 1), max(int(size[1]), 1))
|
||
|
||
# 检查裁剪范围是否超出边界
|
||
x_start = max(int(center[0] - expanded_size[0] / 2), 0)
|
||
x_end = min(int(center[0] + expanded_size[0] / 2), rotated_img.shape[1])
|
||
y_start = max(int(center[1] - expanded_size[1] / 2), 0)
|
||
y_end = min(int(center[1] + expanded_size[1] / 2), rotated_img.shape[0])
|
||
|
||
if x_start >= x_end or y_start >= y_end:
|
||
raise ValueError("裁剪范围无效,可能是输入坐标错误或图片过小。")
|
||
|
||
# 裁剪最小矩形
|
||
cropped_img = rotated_img[y_start:y_end, x_start:x_end]
|
||
|
||
# 如果宽度小于高度,旋转 90°
|
||
if cropped_img.shape[1] < cropped_img.shape[0]:
|
||
cropped_img = cv2.rotate(cropped_img, cv2.ROTATE_90_CLOCKWISE)
|
||
|
||
# 判断是否需要旋转 180°
|
||
h, w, _ = cropped_img.shape
|
||
half_h = h // 2
|
||
|
||
upper_half = cropped_img[:half_h, :, 3] # 提取上半部分的 alpha 通道
|
||
lower_half = cropped_img[half_h:, :, 3] # 提取下半部分的 alpha 通道
|
||
|
||
upper_non_transparent = np.sum(upper_half > 0)
|
||
lower_non_transparent = np.sum(lower_half > 0)
|
||
|
||
# 如果上半部分非透明像素多于下半部分,旋转 180°
|
||
if upper_non_transparent > lower_non_transparent:
|
||
cropped_img = cv2.rotate(cropped_img, cv2.ROTATE_180)
|
||
|
||
|
||
# 检查裁剪结果是否为空
|
||
if cropped_img.size == 0:
|
||
raise ValueError("裁剪结果为空,请检查坐标范围。")
|
||
|
||
# 保存结果
|
||
cv2.imwrite("output13/"+str(is_transparent)+"_"+out_image_file, cropped_img)
|
||
print(f"结果图片已保存到 {output_path}")
|
||
|
||
|
||
def nearest_distance(x1, y1, x2, y2):
|
||
distance = math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
|
||
return distance < 2
|
||
|
||
def split_contours_into_segments(contour, quad):
|
||
|
||
segments = [[],[],[],[]]
|
||
|
||
segment_index = 0
|
||
|
||
flag = [True,True,True,True]
|
||
|
||
offset = []
|
||
|
||
for point in contour:
|
||
point = point[0]
|
||
|
||
if flag[0] & nearest_distance(point[0], point[1], quad[0][0], quad[0][1]):
|
||
segment_index += 1
|
||
flag[0] = False
|
||
if flag[1] & nearest_distance(point[0], point[1], quad[1][0], quad[1][1]):
|
||
segment_index += 1
|
||
flag[1] = False
|
||
if flag[2] & nearest_distance(point[0], point[1], quad[2][0], quad[2][1]):
|
||
segment_index += 1
|
||
flag[2] = False
|
||
if flag[3] & nearest_distance(point[0], point[1], quad[3][0], quad[3][1]):
|
||
segment_index += 1
|
||
flag[3] = False
|
||
|
||
if segment_index >= 4:
|
||
offset.append(point)
|
||
else:
|
||
segments[segment_index].append(point)
|
||
|
||
segments[0] = offset + segments[0]
|
||
return segments
|
||
|
||
|
||
def add_suffix(file_path, suffix):
|
||
# 分割文件名和扩展名
|
||
dir_name, file_name_with_ext = os.path.split(file_path)
|
||
file_name, file_ext = os.path.splitext(file_name_with_ext)
|
||
|
||
# 添加后缀
|
||
new_file_name = f"{file_name}{suffix}{file_ext}"
|
||
|
||
return new_file_name
|
||
|
||
def process_image(image_path, save_path):
|
||
image = cv2.imread(image_path, cv2.IMREAD_UNCHANGED)
|
||
if image is None:
|
||
print(f"无法加载图片: {image_path}")
|
||
return
|
||
|
||
alpha_channel = image[:, :, 3]
|
||
|
||
non_transparent_area = np.count_nonzero(alpha_channel > 0)
|
||
if non_transparent_area == 0:
|
||
print(f"图片完全透明: {image_path}")
|
||
return
|
||
|
||
max_corners = 14
|
||
corners = cv2.goodFeaturesToTrack(alpha_channel, maxCorners=max_corners, qualityLevel=0.01, minDistance=15, blockSize=5)
|
||
if corners is not None:
|
||
corners = np.intp(corners)
|
||
corner_points = [tuple(corner.ravel()) for corner in corners]
|
||
|
||
best_quad, max_weight = find_best_quadrilateral(corner_points, non_transparent_area)
|
||
|
||
if best_quad is None:
|
||
print(f"未找到有效四边形: {image_path}")
|
||
return
|
||
|
||
output_image = cv2.cvtColor(image[:, :, :3], cv2.COLOR_BGR2RGB)
|
||
for point in corner_points:
|
||
cv2.circle(output_image, point, radius=3, color=(255, 0, 0), thickness=-1)
|
||
|
||
#cv2.polylines(output_image, [np.array(best_quad, dtype=np.int32)], isClosed=True, color=(0, 255, 0), thickness=2)
|
||
contours, _ = cv2.findContours(alpha_channel, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
|
||
for contour in contours:
|
||
clockwise = counter_order(contours)
|
||
best_quad = order_points(best_quad, clockwise)
|
||
segments = split_contours_into_segments(contour, best_quad)
|
||
colors = [(255, 0, 0), (0, 255, 0), (0, 0, 255), (255, 255, 0)]
|
||
for i, segment in enumerate(segments):
|
||
if segment:
|
||
cv2.polylines(output_image, [np.array(segment)], isClosed=False, color=colors[i], thickness=2)
|
||
|
||
extract_and_save_pixels(image, segment, add_suffix(save_path,"_" + str(i)))
|
||
|
||
cv2.imwrite(save_path, cv2.cvtColor(output_image, cv2.COLOR_RGB2BGR))
|
||
|
||
else:
|
||
print(f"未检测到角点: {image_path}")
|
||
|
||
for filename in os.listdir(input_folder):
|
||
if filename.lower().endswith((".png", ".jpg", ".jpeg")):
|
||
input_path = os.path.join(input_folder, filename)
|
||
output_path = os.path.join(output_folder, filename)
|
||
process_image(input_path, output_path)
|
||
|
||
end_time = time.time()
|
||
print(f"处理图片的时间: {end_time - start_time:.4f} 秒")
|
||
print(f"所有图片已处理完成,结果保存在: {output_folder}")
|