Files
jigsaw/angle4.py
2024-12-03 11:34:51 +13:00

305 lines
10 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.

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}")