| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| """ |
| This code is refer from: |
| https://github.com/WenmuZhou/DBNet.pytorch/blob/master/data_loader/modules/random_crop_data.py |
| """ |
|
|
| from __future__ import absolute_import |
| from __future__ import division |
| from __future__ import print_function |
| from __future__ import unicode_literals |
|
|
| import numpy as np |
| import cv2 |
| import random |
|
|
| from paddle import get_device |
|
|
| from shapely.geometry import Polygon, box as shapely_box |
| from shapely import intersection |
|
|
|
|
| def is_poly_in_rect(poly, x, y, w, h): |
| poly = np.array(poly) |
| if poly[:, 0].min() < x or poly[:, 0].max() > x + w: |
| return False |
| if poly[:, 1].min() < y or poly[:, 1].max() > y + h: |
| return False |
| return True |
|
|
|
|
| def is_poly_outside_rect(poly, x, y, w, h): |
| poly = np.array(poly) |
| if poly[:, 0].max() < x or poly[:, 0].min() > x + w: |
| return True |
| if poly[:, 1].max() < y or poly[:, 1].min() > y + h: |
| return True |
| return False |
|
|
|
|
| def split_regions(axis): |
| regions = [] |
| min_axis = 0 |
| for i in range(1, axis.shape[0]): |
| if axis[i] != axis[i - 1] + 1: |
| region = axis[min_axis:i] |
| min_axis = i |
| regions.append(region) |
| return regions |
|
|
|
|
| def random_select(axis, max_size): |
| xx = np.random.choice(axis, size=2) |
| xmin = np.min(xx) |
| xmax = np.max(xx) |
| xmin = np.clip(xmin, 0, max_size - 1) |
| xmax = np.clip(xmax, 0, max_size - 1) |
| return xmin, xmax |
|
|
|
|
| def region_wise_random_select(regions, max_size): |
| selected_index = list(np.random.choice(len(regions), 2)) |
| selected_values = [] |
| for index in selected_index: |
| axis = regions[index] |
| xx = int(np.random.choice(axis, size=1)) |
| selected_values.append(xx) |
| xmin = min(selected_values) |
| xmax = max(selected_values) |
| return xmin, xmax |
|
|
|
|
| def get_min_rotated_rect_side(poly): |
| """ |
| Compute the minimum side length of the minimum bounding rotated rectangle of a polygon. |
| """ |
| poly = np.array(poly).astype(np.float32) |
| if len(poly) < 3: |
| return 0 |
| rect = cv2.minAreaRect(poly) |
| width, height = rect[1] |
| return min(width, height) |
|
|
|
|
| def get_min_quad_side(quad): |
| """ |
| Compute the minimum side length of a quadrilateral. |
| """ |
| if len(quad) != 4: |
| return 0 |
| quad = np.array(quad) |
| sides = [] |
| for i in range(4): |
| side = np.linalg.norm(quad[i] - quad[(i + 1) % 4]) |
| sides.append(side) |
| return min(sides) if sides else 0 |
|
|
|
|
| def clip_poly_to_rect(poly, x, y, w, h): |
| """ |
| Clip a polygon to a rectangular region and return the clipped quadrilateral. |
| |
| Args: |
| poly: Original polygon vertices [[x1, y1], [x2, y2], ...] |
| x, y, w, h: Position and size of the clipping rectangle |
| |
| Returns: |
| Clipped quadrilateral vertices, or None if the result is invalid. |
| """ |
| try: |
| |
| poly_shape = Polygon(poly) |
| crop_rect = shapely_box(x, y, x + w, y + h) |
|
|
| |
| clipped = intersection(poly_shape, crop_rect) |
|
|
| |
| if clipped.is_empty: |
| return None |
|
|
| |
| if clipped.geom_type == "Polygon": |
| coords = list(clipped.exterior.coords[:-1]) |
| elif clipped.geom_type == "MultiPolygon": |
| |
| largest = max(clipped.geoms, key=lambda p: p.area) |
| coords = list(largest.exterior.coords[:-1]) |
| elif clipped.geom_type == "GeometryCollection": |
| |
| polygons = [g for g in clipped.geoms if g.geom_type == "Polygon"] |
| if not polygons: |
| return None |
| largest = max(polygons, key=lambda p: p.area) |
| coords = list(largest.exterior.coords[:-1]) |
| else: |
| return None |
|
|
| |
| if len(coords) <= 3: |
| return None |
|
|
| |
| coords = np.array(coords) |
|
|
| |
| if len(coords) == 4: |
| return coords |
|
|
| |
| |
| if len(coords) > 4: |
| poly_cv = coords.reshape(-1, 1, 2).astype(np.float32) |
| peri = cv2.arcLength(poly_cv, True) |
| if peri < 1e-6: |
| return None |
| lo, hi = 0.0, 0.5 |
| best = None |
| for _ in range(50): |
| mid = (lo + hi) / 2 |
| approx = cv2.approxPolyDP(poly_cv, mid * peri, True) |
| if len(approx) <= 4: |
| best = approx |
| hi = mid |
| else: |
| lo = mid |
| if best is not None and len(best) >= 3: |
| return best.reshape(-1, 2) |
| return None |
|
|
| return coords |
| except Exception as e: |
| return None |
|
|
|
|
| class RandomCrop(object): |
| def __init__( |
| self, |
| size=(640, 640), |
| max_tries=10, |
| min_crop_side_ratio=0.1, |
| keep_ratio=True, |
| **kwargs, |
| ): |
| self.size = size |
| self.max_tries = max_tries |
| self.min_crop_side_ratio = min_crop_side_ratio |
| self.keep_ratio = keep_ratio |
|
|
| def __call__(self, data): |
| img = data["image"] |
| text_polys = data["polys"] |
| ignore_tags = data["ignore_tags"] |
| texts = data["texts"] |
|
|
| |
| care_indices = [i for i, tag in enumerate(ignore_tags) if not tag] |
| all_care_polys = [text_polys[i] for i in care_indices] |
|
|
| h, w, _ = img.shape |
|
|
| |
| if len(all_care_polys) == 0: |
| |
| crop_x, crop_y, crop_w, crop_h = 0, 0, w, h |
| valid_care_data = [] |
| else: |
| |
| char_heights = np.array( |
| [get_min_rotated_rect_side(poly) for poly in all_care_polys] |
| ) |
|
|
| |
| valid_care_data = [] |
| for attempt in range(self.max_tries): |
| |
| crop_w_min = min(int(w * self.min_crop_side_ratio), self.size[0]) |
| crop_w_max = int(self.size[0] * 3) |
| crop_w = ( |
| w |
| if crop_w_min >= crop_w_max |
| else min(random.randint(crop_w_min, crop_w_max), w) |
| ) |
|
|
| crop_h_min = min(int(h * self.min_crop_side_ratio), self.size[1]) |
| crop_h_max = int(self.size[1] * 3) |
| crop_h = ( |
| h |
| if crop_h_min >= crop_h_max |
| else min(random.randint(crop_h_min, crop_h_max), h) |
| ) |
|
|
| |
| crop_x = 0 if crop_w >= w else random.randint(0, w - crop_w) |
| crop_y = 0 if crop_h >= h else random.randint(0, h - crop_h) |
|
|
| |
| valid_care_data = [] |
| for care_idx, (poly, char_height) in enumerate( |
| zip(all_care_polys, char_heights) |
| ): |
| |
| if is_poly_outside_rect(poly, crop_x, crop_y, crop_w, crop_h): |
| continue |
|
|
| |
| if is_poly_in_rect(poly, crop_x, crop_y, crop_w, crop_h): |
| valid_care_data.append( |
| (care_idx, None) |
| ) |
| continue |
|
|
| |
| clipped_poly = clip_poly_to_rect( |
| poly, crop_x, crop_y, crop_w, crop_h |
| ) |
| if clipped_poly is None: |
| continue |
|
|
| |
| clipped_area = cv2.contourArea(clipped_poly.astype(np.float32)) |
| if clipped_area < 80: |
| continue |
|
|
| |
| clipped_char_height = get_min_rotated_rect_side(clipped_poly) |
| if clipped_char_height < char_height * 0.35: |
| continue |
|
|
| |
| if len(clipped_poly) == 4: |
| min_side = get_min_quad_side(clipped_poly) |
| if min_side < char_height * 0.35: |
| continue |
|
|
| |
| valid_care_data.append((care_idx, clipped_poly)) |
|
|
| |
| if len(valid_care_data) >= 1: |
| break |
| else: |
| |
| crop_x, crop_y, crop_w, crop_h = 0, 0, w, h |
| valid_care_data = [(i, None) for i in range(len(all_care_polys))] |
|
|
| |
| |
| need_resize = crop_w > self.size[0] or crop_h > self.size[1] |
|
|
| if need_resize: |
| |
| scale_w = self.size[0] / crop_w |
| scale_h = self.size[1] / crop_h |
| scale = min(scale_w, scale_h) |
| h_resized = int(crop_h * scale) |
| w_resized = int(crop_w * scale) |
| else: |
| |
| scale = 1.0 |
| h_resized = crop_h |
| w_resized = crop_w |
|
|
| if self.keep_ratio: |
| |
| pad_h = self.size[1] - h_resized |
| pad_w = self.size[0] - w_resized |
|
|
| |
| pad_top = random.randint(0, pad_h) if pad_h > 0 else 0 |
| pad_left = random.randint(0, pad_w) if pad_w > 0 else 0 |
|
|
| |
| cropped_img = img[crop_y : crop_y + crop_h, crop_x : crop_x + crop_w] |
| if need_resize: |
| resized_img = cv2.resize(cropped_img, (w_resized, h_resized)) |
| else: |
| resized_img = cropped_img |
|
|
| |
| padimg = np.zeros((self.size[1], self.size[0], img.shape[2]), img.dtype) |
| padimg[pad_top : pad_top + h_resized, pad_left : pad_left + w_resized] = ( |
| resized_img |
| ) |
| img = padimg |
| else: |
| img = cv2.resize( |
| img[crop_y : crop_y + crop_h, crop_x : crop_x + crop_w], |
| tuple(self.size), |
| ) |
| pad_left = 0 |
| pad_top = 0 |
|
|
| |
| valid_care_indices_set = {care_idx for care_idx, _ in valid_care_data} |
|
|
| |
| care_idx_to_clipped = { |
| care_idx: clipped for care_idx, clipped in valid_care_data |
| } |
|
|
| |
| text_polys_crop = [] |
| ignore_tags_crop = [] |
| texts_crop = [] |
|
|
| for all_idx, (poly, text, tag) in enumerate( |
| zip(text_polys, texts, ignore_tags) |
| ): |
| if tag: |
| |
| if not is_poly_outside_rect(poly, crop_x, crop_y, crop_w, crop_h): |
| adjusted_poly = (poly - (crop_x, crop_y)) * scale + ( |
| pad_left, |
| pad_top, |
| ) |
| adjusted_poly[:, 0] = np.clip(adjusted_poly[:, 0], 0, self.size[0]) |
| adjusted_poly[:, 1] = np.clip(adjusted_poly[:, 1], 0, self.size[1]) |
| text_polys_crop.append(adjusted_poly.tolist()) |
| ignore_tags_crop.append(tag) |
| texts_crop.append(text) |
| else: |
| |
| try: |
| care_idx = care_indices.index(all_idx) |
| except ValueError: |
| continue |
|
|
| |
| if care_idx not in valid_care_indices_set: |
| continue |
|
|
| |
| clipped_poly = care_idx_to_clipped[care_idx] |
|
|
| if clipped_poly is None: |
| |
| adjusted_poly = (poly - (crop_x, crop_y)) * scale + ( |
| pad_left, |
| pad_top, |
| ) |
| else: |
| |
| adjusted_poly = (clipped_poly - (crop_x, crop_y)) * scale + ( |
| pad_left, |
| pad_top, |
| ) |
|
|
| text_polys_crop.append(adjusted_poly.tolist()) |
| ignore_tags_crop.append(tag) |
| texts_crop.append(text) |
| data["image"] = img |
| |
| if text_polys_crop: |
| max_points = max(len(p) for p in text_polys_crop) |
| for i, poly in enumerate(text_polys_crop): |
| if len(poly) < max_points: |
| text_polys_crop[i] = poly + [poly[-1]] * (max_points - len(poly)) |
| data["polys"] = np.array(text_polys_crop, dtype=np.float32) |
| data["ignore_tags"] = ignore_tags_crop |
| data["texts"] = texts_crop |
| return data |
|
|
|
|
| def crop_area(im, text_polys, min_crop_side_ratio, max_tries): |
| h, w, _ = im.shape |
| h_array = np.zeros(h, dtype=np.int32) |
| w_array = np.zeros(w, dtype=np.int32) |
| for points in text_polys: |
| points = np.round(points, decimals=0).astype(np.int32) |
| minx = np.min(points[:, 0]) |
| maxx = np.max(points[:, 0]) |
| w_array[minx:maxx] = 1 |
| miny = np.min(points[:, 1]) |
| maxy = np.max(points[:, 1]) |
| h_array[miny:maxy] = 1 |
| |
| h_axis = np.where(h_array == 0)[0] |
| w_axis = np.where(w_array == 0)[0] |
|
|
| if len(h_axis) == 0 or len(w_axis) == 0: |
| return 0, 0, w, h |
|
|
| h_regions = split_regions(h_axis) |
| w_regions = split_regions(w_axis) |
|
|
| for i in range(max_tries): |
| if len(w_regions) > 1: |
| xmin, xmax = region_wise_random_select(w_regions, w) |
| else: |
| xmin, xmax = random_select(w_axis, w) |
| if len(h_regions) > 1: |
| ymin, ymax = region_wise_random_select(h_regions, h) |
| else: |
| ymin, ymax = random_select(h_axis, h) |
|
|
| if ( |
| xmax - xmin < min_crop_side_ratio * w |
| or ymax - ymin < min_crop_side_ratio * h |
| ): |
| |
| continue |
| num_poly_in_rect = 0 |
| for poly in text_polys: |
| if not is_poly_outside_rect(poly, xmin, ymin, xmax - xmin, ymax - ymin): |
| num_poly_in_rect += 1 |
| break |
|
|
| if num_poly_in_rect > 0: |
| return xmin, ymin, xmax - xmin, ymax - ymin |
|
|
| return 0, 0, w, h |
|
|
|
|
| class EastRandomCropData(object): |
| def __init__( |
| self, |
| size=(640, 640), |
| max_tries=10, |
| min_crop_side_ratio=0.1, |
| keep_ratio=True, |
| **kwargs, |
| ): |
| self.size = size |
| self.max_tries = max_tries |
| self.min_crop_side_ratio = min_crop_side_ratio |
| self.keep_ratio = keep_ratio |
|
|
| def __call__(self, data): |
| img = data["image"] |
| text_polys = data["polys"] |
| ignore_tags = data["ignore_tags"] |
| texts = data["texts"] |
| all_care_polys = [text_polys[i] for i, tag in enumerate(ignore_tags) if not tag] |
| |
| crop_x, crop_y, crop_w, crop_h = crop_area( |
| img, all_care_polys, self.min_crop_side_ratio, self.max_tries |
| ) |
| |
| scale_w = self.size[0] / crop_w |
| scale_h = self.size[1] / crop_h |
| scale = min(scale_w, scale_h) |
| h = int(crop_h * scale) |
| w = int(crop_w * scale) |
| if self.keep_ratio: |
| padimg = np.zeros((self.size[1], self.size[0], img.shape[2]), img.dtype) |
| padimg[:h, :w] = cv2.resize( |
| img[crop_y : crop_y + crop_h, crop_x : crop_x + crop_w], (w, h) |
| ) |
| img = padimg |
| else: |
| img = cv2.resize( |
| img[crop_y : crop_y + crop_h, crop_x : crop_x + crop_w], |
| tuple(self.size), |
| ) |
| |
| text_polys_crop = [] |
| ignore_tags_crop = [] |
| texts_crop = [] |
| for poly, text, tag in zip(text_polys, texts, ignore_tags): |
| poly = ((poly - (crop_x, crop_y)) * scale).tolist() |
| if not is_poly_outside_rect(poly, 0, 0, w, h): |
| text_polys_crop.append(poly) |
| ignore_tags_crop.append(tag) |
| texts_crop.append(text) |
| data["image"] = img |
| data["polys"] = np.array(text_polys_crop) |
| if "iluvatar_gpu" in get_device(): |
| data["polys"] = np.array(text_polys_crop).astype(np.float32) |
| data["ignore_tags"] = ignore_tags_crop |
| data["texts"] = texts_crop |
| return data |
|
|
|
|
| class RandomCropImgMask(object): |
| def __init__(self, size, main_key, crop_keys, p=3 / 8, **kwargs): |
| self.size = size |
| self.main_key = main_key |
| self.crop_keys = crop_keys |
| self.p = p |
|
|
| def __call__(self, data): |
| image = data["image"] |
|
|
| h, w = image.shape[0:2] |
| th, tw = self.size |
| if w == tw and h == th: |
| return data |
|
|
| mask = data[self.main_key] |
| if np.max(mask) > 0 and random.random() > self.p: |
| |
| tl = np.min(np.where(mask > 0), axis=1) - (th, tw) |
| tl[tl < 0] = 0 |
| br = np.max(np.where(mask > 0), axis=1) - (th, tw) |
| br[br < 0] = 0 |
|
|
| br[0] = min(br[0], h - th) |
| br[1] = min(br[1], w - tw) |
|
|
| i = random.randint(tl[0], br[0]) if tl[0] < br[0] else 0 |
| j = random.randint(tl[1], br[1]) if tl[1] < br[1] else 0 |
| else: |
| i = random.randint(0, h - th) if h - th > 0 else 0 |
| j = random.randint(0, w - tw) if w - tw > 0 else 0 |
|
|
| |
| for k in data: |
| if k in self.crop_keys: |
| if len(data[k].shape) == 3: |
| if np.argmin(data[k].shape) == 0: |
| img = data[k][:, i : i + th, j : j + tw] |
| if img.shape[1] != img.shape[2]: |
| a = 1 |
| elif np.argmin(data[k].shape) == 2: |
| img = data[k][i : i + th, j : j + tw, :] |
| if img.shape[1] != img.shape[0]: |
| a = 1 |
| else: |
| img = data[k] |
| else: |
| img = data[k][i : i + th, j : j + tw] |
| if img.shape[0] != img.shape[1]: |
| a = 1 |
| data[k] = img |
| return data |
|
|