UnisMindMap/mineru/model/mfr/unimernet/Unimernet.py

208 lines
7.7 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import torch
from torch.utils.data import DataLoader, Dataset
from tqdm import tqdm
from mineru.utils.boxbase import calculate_iou
class MathDataset(Dataset):
def __init__(self, image_paths, transform=None):
self.image_paths = image_paths
self.transform = transform
def __len__(self):
return len(self.image_paths)
def __getitem__(self, idx):
raw_image = self.image_paths[idx]
if self.transform:
image = self.transform(raw_image)
return image
class UnimernetModel(object):
def __init__(self, weight_dir, _device_="cpu"):
from .unimernet_hf import UnimernetModel
if _device_.startswith("mps") or _device_.startswith("npu"):
self.model = UnimernetModel.from_pretrained(weight_dir, attn_implementation="eager")
else:
self.model = UnimernetModel.from_pretrained(weight_dir)
self.device = _device_
self.model.to(_device_)
if not _device_.startswith("cpu"):
self.model = self.model.to(dtype=torch.float16)
self.model.eval()
@staticmethod
def _filter_boxes_by_iou(xyxy, conf, cla, iou_threshold=0.8):
"""过滤IOU超过阈值的重叠框保留置信度较高的框。
Args:
xyxy: 框坐标张量shape为(N, 4)
conf: 置信度张量shape为(N,)
cla: 类别张量shape为(N,)
iou_threshold: IOU阈值默认0.9
Returns:
过滤后的xyxy, conf, cla张量
"""
if len(xyxy) == 0:
return xyxy, conf, cla
# 转换为CPU进行处理
xyxy_cpu = xyxy.cpu()
conf_cpu = conf.cpu()
n = len(xyxy_cpu)
keep = [True] * n
for i in range(n):
if not keep[i]:
continue
bbox1 = xyxy_cpu[i].tolist()
for j in range(i + 1, n):
if not keep[j]:
continue
bbox2 = xyxy_cpu[j].tolist()
iou = calculate_iou(bbox1, bbox2)
if iou > iou_threshold:
# 保留置信度较高的框
if conf_cpu[i] >= conf_cpu[j]:
keep[j] = False
else:
keep[i] = False
break # i被删除跳出内循环
keep_indices = [i for i in range(n) if keep[i]]
if len(keep_indices) == n:
return xyxy, conf, cla
keep_indices = torch.tensor(keep_indices, dtype=torch.long)
return xyxy[keep_indices], conf[keep_indices], cla[keep_indices]
def predict(self, mfd_res, image):
formula_list = []
mf_image_list = []
# 对检测框进行IOU去重保留置信度较高的框
xyxy_filtered, conf_filtered, cla_filtered = self._filter_boxes_by_iou(
mfd_res.boxes.xyxy, mfd_res.boxes.conf, mfd_res.boxes.cls
)
for xyxy, conf, cla in zip(
xyxy_filtered.cpu(), conf_filtered.cpu(), cla_filtered.cpu()
):
xmin, ymin, xmax, ymax = [int(p.item()) for p in xyxy]
new_item = {
"category_id": 13 + int(cla.item()),
"poly": [xmin, ymin, xmax, ymin, xmax, ymax, xmin, ymax],
"score": round(float(conf.item()), 2),
"latex": "",
}
formula_list.append(new_item)
bbox_img = image[ymin:ymax, xmin:xmax]
mf_image_list.append(bbox_img)
dataset = MathDataset(mf_image_list, transform=self.model.transform)
dataloader = DataLoader(dataset, batch_size=32, num_workers=0)
mfr_res = []
for mf_img in dataloader:
mf_img = mf_img.to(dtype=self.model.dtype)
mf_img = mf_img.to(self.device)
with torch.no_grad():
output = self.model.generate({"image": mf_img})
mfr_res.extend(output["fixed_str"])
for res, latex in zip(formula_list, mfr_res):
res["latex"] = latex
return formula_list
def batch_predict(
self,
images_mfd_res: list,
images: list,
batch_size: int = 64,
interline_enable: bool = True,
) -> list:
images_formula_list = []
mf_image_list = []
backfill_list = []
image_info = [] # Store (area, original_index, image) tuples
# Collect images with their original indices
for image_index in range(len(images_mfd_res)):
mfd_res = images_mfd_res[image_index]
image = images[image_index]
formula_list = []
# 对检测框进行IOU去重保留置信度较高的框
xyxy_filtered, conf_filtered, cla_filtered = self._filter_boxes_by_iou(
mfd_res.boxes.xyxy, mfd_res.boxes.conf, mfd_res.boxes.cls
)
for idx, (xyxy, conf, cla) in enumerate(zip(
xyxy_filtered, conf_filtered, cla_filtered
)):
if not interline_enable and cla.item() == 1:
continue # Skip interline regions if not enabled
xmin, ymin, xmax, ymax = [int(p.item()) for p in xyxy]
new_item = {
"category_id": 13 + int(cla.item()),
"poly": [xmin, ymin, xmax, ymin, xmax, ymax, xmin, ymax],
"score": round(float(conf.item()), 2),
"latex": "",
}
formula_list.append(new_item)
bbox_img = image[ymin:ymax, xmin:xmax]
area = (xmax - xmin) * (ymax - ymin)
curr_idx = len(mf_image_list)
image_info.append((area, curr_idx, bbox_img))
mf_image_list.append(bbox_img)
images_formula_list.append(formula_list)
backfill_list += formula_list
# Stable sort by area
image_info.sort(key=lambda x: x[0]) # sort by area
sorted_indices = [x[1] for x in image_info]
sorted_images = [x[2] for x in image_info]
# Create mapping for results
index_mapping = {new_idx: old_idx for new_idx, old_idx in enumerate(sorted_indices)}
# Create dataset with sorted images
dataset = MathDataset(sorted_images, transform=self.model.transform)
# 如果batch_size > len(sorted_images)则设置为不超过len(sorted_images)的2的幂
batch_size = min(batch_size, max(1, 2 ** (len(sorted_images).bit_length() - 1))) if sorted_images else 1
dataloader = DataLoader(dataset, batch_size=batch_size, num_workers=0)
# Process batches and store results
mfr_res = []
# for mf_img in dataloader:
with tqdm(total=len(sorted_images), desc="MFR Predict") as pbar:
for index, mf_img in enumerate(dataloader):
mf_img = mf_img.to(dtype=self.model.dtype)
mf_img = mf_img.to(self.device)
with torch.no_grad():
output = self.model.generate({"image": mf_img}, batch_size=batch_size)
mfr_res.extend(output["fixed_str"])
# 更新进度条每次增加batch_size但要注意最后一个batch可能不足batch_size
current_batch_size = min(batch_size, len(sorted_images) - index * batch_size)
pbar.update(current_batch_size)
# Restore original order
unsorted_results = [""] * len(mfr_res)
for new_idx, latex in enumerate(mfr_res):
original_idx = index_mapping[new_idx]
unsorted_results[original_idx] = latex
# Fill results back
for res, latex in zip(backfill_list, unsorted_results):
res["latex"] = latex
return images_formula_list