import os
import cv2
import numpy as np
import matplotlib.pyplot as plt
from tqdm import tqdm
from PIL import Image
from scipy import ndimage
from skimage.feature import canny
from skimage.filters import sobel
from skimage.measure import label, regionprops
# 设置中文字体
plt.rcParams["font.family"] = ["SimHei", "sans-serif"]
plt.rcParams["axes.unicode_minus"] = False # 确保负号正确显示
class FragmentReconstructor:
def __init__(self, folder_path, rows=11, cols=19, edge_width=15):
self.folder_path = folder_path
self.rows = rows
self.cols = cols
self.edge_width = edge_width
self.fragments = []
self.grid = np.full((rows, cols), -1, dtype=int)
self.used_fragments = set()
self.similarity_matrix = None
self.background_color = 255 # 假设背景为白色
def load_fragments(self):
print(f"正在加载碎片图像从: {self.folder_path}")
self.fragments = []
loaded_count = 0
for filename in tqdm(os.listdir(self.folder_path)):
if not filename.lower().endswith(('.bmp', '.png', '.jpg', '.jpeg')):
continue
filepath = os.path.join(self.folder_path, filename)
try:
# 使用PIL打开图像
img = Image.open(filepath)
img = img.convert('L') # 转为灰度
img_array = np.array(img)
# 自适应二值化处理
binary = cv2.adaptiveThreshold(
img_array, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
cv2.THRESH_BINARY, 11, 2
)
# 计算图像特征
edges = self.extract_edges(img_array)
self.fragments.append({
'filename': filename,
'image': img_array, # 灰度图像
'binary': binary, # 二值图像
'edges': edges # 边缘特征
})
loaded_count += 1
except Exception as e:
print(f"无法读取图像 {filename}: {str(e)}")
print(f"成功加载 {loaded_count}/{len(os.listdir(self.folder_path))} 个碎片")
return loaded_count > 0
def extract_edges(self, img):
"""提取碎片的四个边缘特征 - 改进版"""
h, w = img.shape
# 使用Canny边缘检测
edges = canny(img, sigma=1)
# 提取边缘区域
top_region = edges[0:self.edge_width, :]
bottom_region = edges[h - self.edge_width:h, :]
left_region = edges[:, 0:self.edge_width]
right_region = edges[:, w - self.edge_width:w]
# 计算边缘密度
top_density = np.mean(top_region)
bottom_density = np.mean(bottom_region)
left_density = np.mean(left_region)
right_density = np.mean(right_region)
# 计算边缘方向直方图
top_hist = self.edge_orientation_histogram(img[0:self.edge_width, :])
bottom_hist = self.edge_orientation_histogram(img[h - self.edge_width:h, :])
left_hist = self.edge_orientation_histogram(img[:, 0:self.edge_width])
right_hist = self.edge_orientation_histogram(img[:, w - self.edge_width:w])
return {
'top': {'density': top_density, 'hist': top_hist},
'bottom': {'density': bottom_density, 'hist': bottom_hist},
'left': {'density': left_density, 'hist': left_hist},
'right': {'density': right_density, 'hist': right_hist}
}
def edge_orientation_histogram(self, region):
"""计算边缘方向直方图"""
# 使用Sobel算子计算梯度
dx = sobel(region, axis=0, mode='reflect')
dy = sobel(region, axis=1, mode='reflect')
# 计算梯度幅度和方向
magnitude = np.hypot(dx, dy)
angle = np.arctan2(dy, dx) * (180 / np.pi) # 转换为角度
# 创建8-bin直方图 (0-45, 45-90, 90-135, 135-180, 180-225, 225-270, 270-315, 315-360)
hist, _ = np.histogram(angle[magnitude > 0.1], bins=8, range=(-180, 180))
# 归一化
if np.sum(hist) > 0:
hist = hist / np.sum(hist)
return hist
def compute_similarity(self):
"""计算碎片之间的相似度矩阵 - 改进版"""
n = len(self.fragments)
# 4个方向: 0=下边, 1=上边, 2=右边, 3=左边
self.similarity_matrix = np.zeros((n, n, 4))
print("计算碎片相似度矩阵(使用混合特征)...")
for i in tqdm(range(n)):
for j in range(n):
if i == j:
continue
# 计算四个方向的相似度
# 方向0: i的底部 vs j的顶部
sim_bottom_top = self.edge_similarity(
self.fragments[i]['edges']['bottom'],
self.fragments[j]['edges']['top']
)
# 方向1: i的顶部 vs j的底部
sim_top_bottom = self.edge_similarity(
self.fragments[i]['edges']['top'],
self.fragments[j]['edges']['bottom']
)
# 方向2: i的右侧 vs j的左侧
sim_right_left = self.edge_similarity(
self.fragments[i]['edges']['right'],
self.fragments[j]['edges']['left']
)
# 方向3: i的左侧 vs j的右侧
sim_left_right = self.edge_similarity(
self.fragments[i]['edges']['left'],
self.fragments[j]['edges']['right']
)
self.similarity_matrix[i, j, 0] = sim_bottom_top
self.similarity_matrix[i, j, 1] = sim_top_bottom
self.similarity_matrix[i, j, 2] = sim_right_left
self.similarity_matrix[i, j, 3] = sim_left_right
def edge_similarity(self, edge1, edge2):
"""计算两个边缘的相似度"""
# 密度相似度
density_sim = 1 - abs(edge1['density'] - edge2['density'])
# 直方图相似度(余弦相似度)
hist_sim = np.dot(edge1['hist'], edge2['hist']) / (
np.linalg.norm(edge1['hist']) * np.linalg.norm(edge2['hist']) + 1e-8
)
# 综合相似度
return 0.7 * hist_sim + 0.3 * density_sim
def find_top_left_corner(self):
"""改进的左上角碎片检测方法 - 使用连通区域分析"""
if not self.fragments:
return -1
top_left_id = 0
min_content_score = float('inf')
for i, frag in enumerate(self.fragments):
img = frag['binary']
# 反转二值图像(使内容为白色,背景为黑色)
inverted = 255 - img
# 标记连通区域
labeled, num_labels = label(inverted, connectivity=2, return_num=True)
# 获取区域属性
regions = regionprops(labeled)
# 计算左上角区域内容量
# 左上角区域定义为图像的左上1/4部分
h, w = img.shape
top_left_region = inverted[0:h // 2, 0:w // 2]
content_score = np.mean(top_left_region > 128)
# 计算边界背景比例
top_bg = np.mean(img[0, :] == self.background_color)
left_bg = np.mean(img[:, 0] == self.background_color)
# 左上角应该有最少的左上角内容和最多的边界背景
score = content_score + (1 - top_bg) + (1 - left_bg)
if score < min_content_score:
min_content_score = score
top_left_id = i
return top_left_id
def find_best_match(self, current_id, direction, threshold=0.3):
"""
寻找最佳匹配的碎片 - 改进版
direction:
'right' - 匹配当前碎片的右边
'bottom' - 匹配当前碎片的下边
"""
if direction == 'right':
edge_type = 2 # 当前碎片的右边缘匹配候选碎片的左边缘 (索引2)
elif direction == 'bottom':
edge_type = 0 # 当前碎片的下边缘匹配候选碎片的上边缘 (索引0)
else:
return None
# 获取所有未使用碎片的相似度分数
scores = []
candidates = []
for j in range(len(self.fragments)):
if j in self.used_fragments or j == current_id:
continue
score = self.similarity_matrix[current_id, j, edge_type]
scores.append(score)
candidates.append(j)
if not candidates:
return None
# 找到最佳候选
best_idx = np.argmax(scores)
best_score = scores[best_idx]
best_candidate = candidates[best_idx]
# 如果最高分低于阈值,返回None
if best_score < threshold:
print(f"警告: 最佳匹配分数({best_score:.2f})低于阈值({threshold})")
return None
# 检查是否有多个高相似度候选
sorted_scores = np.sort(scores)[::-1]
if len(sorted_scores) > 1 and sorted_scores[0] - sorted_scores[1] < 0.1:
print(f"多个候选碎片相似度接近: {sorted_scores[:3]}")
return self.manual_intervention(current_id, best_candidate, candidates, direction)
return best_candidate
def manual_intervention(self, current_id, auto_choice, candidates, direction):
"""人工干预:显示候选碎片供用户选择 - 改进版"""
print("\n=== 需要人工干预 ===")
print(f"当前碎片: {self.fragments[current_id]['filename']}")
print(f"方向: {direction}")
print("候选碎片:")
# 显示当前碎片和边缘特征
plt.figure(figsize=(20, 10))
# 当前碎片
plt.subplot(3, 8, 1)
plt.imshow(self.fragments[current_id]['image'], cmap='gray')
plt.title(f"当前碎片\n{self.fragments[current_id]['filename']}")
# 当前碎片的边缘
if direction == 'right':
edge_img = self.fragments[current_id]['image'][:, -self.edge_width:]
plt.subplot(3, 8, 2)
plt.imshow(edge_img, cmap='gray')
plt.title("右边缘特征")
elif direction == 'bottom':
edge_img = self.fragments[current_id]['image'][-self.edge_width:, :]
plt.subplot(3, 8, 2)
plt.imshow(edge_img, cmap='gray')
plt.title("下边缘特征")
# 显示候选碎片及其匹配边缘
for i, cand_id in enumerate(candidates[:7]): # 最多显示7个候选
plt.subplot(3, 8, i + 3)
plt.imshow(self.fragments[cand_id]['image'], cmap='gray')
plt.title(f"候选 {i + 1}\n{self.fragments[cand_id]['filename']}")
# 显示匹配边缘
if direction == 'right':
edge_img = self.fragments[cand_id]['image'][:, :self.edge_width]
plt.subplot(3, 8, i + 10)
plt.imshow(edge_img, cmap='gray')
plt.title(f"左边缘")
elif direction == 'bottom':
edge_img = self.fragments[cand_id]['image'][:self.edge_width, :]
plt.subplot(3, 8, i + 10)
plt.imshow(edge_img, cmap='gray')
plt.title(f"上边缘")
plt.tight_layout()
plt.show()
# 获取用户输入
choice = input("请输入选择的候选编号 (1-7),或输入a接受自动选择: ")
if choice.lower() == 'a':
return auto_choice
try:
choice_idx = int(choice) - 1
if 0 <= choice_idx < len(candidates):
return candidates[choice_idx]
except:
pass
return auto_choice
def reconstruct_grid(self):
"""重建碎片网格 - 改进版"""
# 步骤1: 找到左上角碎片
top_left_id = self.find_top_left_corner()
if top_left_id == -1:
print("无法找到左上角碎片")
return False
print(f"选择左上角碎片: {self.fragments[top_left_id]['filename']}")
self.grid[0, 0] = top_left_id
self.used_fragments.add(top_left_id)
# 步骤2: 逐行重建,允许跳过位置
for row in range(self.rows):
for col in range(self.cols):
# 跳过左上角碎片(已放置)
if row == 0 and col == 0:
continue
# 尝试匹配左侧碎片(如果在同一行且不是第一列)
if col > 0:
left_id = self.grid[row, col - 1]
if left_id != -1:
best_match = self.find_best_match(left_id, 'right')
if best_match is not None:
self.grid[row, col] = best_match
self.used_fragments.add(best_match)
continue
# 尝试匹配上方碎片(如果在同一列且不是第一行)
if row > 0:
above_id = self.grid[row - 1, col]
if above_id != -1:
best_match = self.find_best_match(above_id, 'bottom')
if best_match is not None:
self.grid[row, col] = best_match
self.used_fragments.add(best_match)
continue
# 如果两个方向都匹配失败,标记为缺失
print(f"警告: 无法为位置({row},{col})找到匹配碎片")
self.grid[row, col] = -1
return True
def visualize_result(self, output_path):
"""可视化并保存拼接结果 - 改进版"""
if not os.path.exists(output_path):
os.makedirs(output_path)
# 获取碎片尺寸
frag_height, frag_width = self.fragments[0]['image'].shape
full_height = self.rows * frag_height
full_width = self.cols * frag_width
# 创建完整图像
full_image = np.zeros((full_height, full_width), dtype=np.uint8)
grid_image = np.zeros((full_height, full_width, 3), dtype=np.uint8) # 彩色网格
# 填充图像并绘制网格
for row in range(self.rows):
for col in range(self.cols):
frag_id = self.grid[row, col]
y_start = row * frag_height
y_end = y_start + frag_height
x_start = col * frag_width
x_end = x_start + frag_width
if frag_id != -1:
img = self.fragments[frag_id]['image']
full_image[y_start:y_end, x_start:x_end] = img
# 在彩色图像中绘制碎片
grid_image[y_start:y_end, x_start:x_end, 0] = img
grid_image[y_start:y_end, x_start:x_end, 1] = img
grid_image[y_start:y_end, x_start:x_end, 2] = img
# 绘制边界
if col > 0 and self.grid[row, col - 1] != -1:
grid_image[y_start:y_end, x_start, :] = [255, 0, 0] # 红色左边界
if row > 0 and self.grid[row - 1, col] != -1:
grid_image[y_start, x_start:x_end, :] = [0, 0, 255] # 蓝色上边界
else:
# 绘制缺失区域
grid_image[y_start:y_end, x_start:x_end, :] = [255, 255, 0] # 黄色
# 保存图像
output_img_path = os.path.join(output_path, "reconstructed_image.bmp")
cv2.imwrite(output_img_path, full_image)
print(f"保存完整图像到: {output_img_path}")
grid_img_path = os.path.join(output_path, "reconstruction_grid.png")
cv2.imwrite(grid_img_path, grid_image)
print(f"保存网格图像到: {grid_img_path}")
# 显示结果
plt.figure(figsize=(15, 10))
plt.subplot(1, 2, 1)
plt.imshow(full_image, cmap='gray')
plt.title("拼接复原结果")
plt.subplot(1, 2, 2)
plt.imshow(grid_image)
plt.title("重建网格可视化")
plt.tight_layout()
plt.show()
# 保存网格表格
grid_table = np.array([[self.fragments[self.grid[r, c]]['filename']
if self.grid[r, c] != -1 else "Missing"
for c in range(self.cols)]
for r in range(self.rows)])
# 保存为CSV文件
csv_path = os.path.join(output_path, "reconstruction_grid.csv")
np.savetxt(csv_path, grid_table, delimiter=",", fmt="%s")
print(f"保存网格表格到: {csv_path}")
# 打印网格
print("\n复原网格:")
for r in range(self.rows):
print(" | ".join([f"{self.fragments[self.grid[r, c]]['filename']:>10}"
if self.grid[r, c] != -1 else "Missing".center(10)
for c in range(self.cols)]))
def reconstruct(self, output_folder="output"):
"""执行完整重建流程"""
if not self.load_fragments():
print("加载碎片失败,无法继续重建")
return
print("计算碎片相似度矩阵...")
self.compute_similarity()
print("开始重建碎片网格...")
success = self.reconstruct_grid()
if success:
self.visualize_result(output_folder)
else:
print("重建失败,请检查错误或进行更多人工干预")
# 主程序
if __name__ == "__main__":
# 处理中文碎片
print("=" * 50)
print("开始处理中文碎片 (附件3)")
chinese_reconstructor = FragmentReconstructor(
folder_path=r"D:\学习文件\JM\2013B\附件3",
rows=11,
cols=19,
edge_width=20 # 增大边缘宽度以捕获更多特征
)
chinese_reconstructor.reconstruct(output_folder="output_chinese")
# 处理英文碎片
print("=" * 50)
print("开始处理英文碎片 (附件4)")
english_reconstructor = FragmentReconstructor(
folder_path=r"D:\学习文件\JM\2013B\附件4",
rows=11,
cols=19
)
english_reconstructor.reconstruct(output_folder="output_english")
第一张图片正确答案应该为049.bmp,候选图片中也没有正确的答案,不应该是对文件中的图片顺序排列的,怎么修改出正确的代码
最新发布