feat: HSAP platform v2 — modular navigation, quality review, audit log, world model simulation
Major changes: - New frontend (platform/web/): Vite + React 18 + TypeScript + Tailwind - 4-module navigation: 数据送标 / 模型管理 / 车队管理 / 系统管理 - Data catalog with charts (DMS/ADAS/Lane 3-tab view) - Quality review workflow (标注质检): Good/Fine/Bad scoring with auto-advance - Audit enhancements: batch operations, rejection categories, Feishu notifications - Operation audit log (操作日志) - World model simulation studio (仿真工坊) - Dataset version management with snapshots and diff - ADAS 7-class dataset integration (138K images organized + compressed) - User management with Feishu integration and pagination - CRUD/search/filter on all pages, card layout redesign - PIL-optimized image overlay rendering - Auto-snapshot on build, in_review workflow stage - Removed embedded algorithm code (now in workspace)
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
from .transforms import (RandomLROffsetLABEL, RandomUDoffsetLABEL, Resize,
|
||||
RandomCrop, CenterCrop, RandomRotation, RandomBlur,
|
||||
RandomHorizontalFlip, Normalize, ToTensor)
|
||||
|
||||
from .generate_lane_line import GenerateLaneLine
|
||||
from .process import Process
|
||||
|
||||
__all__ = [
|
||||
'Process',
|
||||
'RandomLROffsetLABEL',
|
||||
'RandomUDoffsetLABEL',
|
||||
'Resize',
|
||||
'RandomCrop',
|
||||
'CenterCrop',
|
||||
'RandomRotation',
|
||||
'RandomBlur',
|
||||
'RandomHorizontalFlip',
|
||||
'Normalize',
|
||||
'ToTensor',
|
||||
'GenerateLaneLine',
|
||||
]
|
||||
@@ -0,0 +1,218 @@
|
||||
import math
|
||||
import numpy as np
|
||||
import cv2
|
||||
import imgaug.augmenters as iaa
|
||||
from imgaug.augmentables.lines import LineString, LineStringsOnImage
|
||||
from imgaug.augmentables.segmaps import SegmentationMapsOnImage
|
||||
from scipy.interpolate import InterpolatedUnivariateSpline
|
||||
from clrnet.datasets.process.transforms import CLRTransforms
|
||||
|
||||
from ..registry import PROCESS
|
||||
|
||||
|
||||
@PROCESS.register_module
|
||||
class GenerateLaneLine(object):
|
||||
def __init__(self, transforms=None, cfg=None, training=True):
|
||||
self.transforms = transforms
|
||||
self.img_w, self.img_h = cfg.img_w, cfg.img_h
|
||||
self.num_points = cfg.num_points
|
||||
self.n_offsets = cfg.num_points
|
||||
self.n_strips = cfg.num_points - 1
|
||||
self.strip_size = self.img_h / self.n_strips
|
||||
self.max_lanes = cfg.max_lanes
|
||||
self.offsets_ys = np.arange(self.img_h, -1, -self.strip_size)
|
||||
self.training = training
|
||||
|
||||
if transforms is None:
|
||||
transforms = CLRTransforms(self.img_h, self.img_w)
|
||||
|
||||
if transforms is not None:
|
||||
img_transforms = []
|
||||
for aug in transforms:
|
||||
p = aug['p']
|
||||
if aug['name'] != 'OneOf':
|
||||
img_transforms.append(
|
||||
iaa.Sometimes(p=p,
|
||||
then_list=getattr(
|
||||
iaa,
|
||||
aug['name'])(**aug['parameters'])))
|
||||
else:
|
||||
img_transforms.append(
|
||||
iaa.Sometimes(
|
||||
p=p,
|
||||
then_list=iaa.OneOf([
|
||||
getattr(iaa,
|
||||
aug_['name'])(**aug_['parameters'])
|
||||
for aug_ in aug['transforms']
|
||||
])))
|
||||
else:
|
||||
img_transforms = []
|
||||
self.transform = iaa.Sequential(img_transforms)
|
||||
|
||||
def lane_to_linestrings(self, lanes):
|
||||
lines = []
|
||||
for lane in lanes:
|
||||
lines.append(LineString(lane))
|
||||
|
||||
return lines
|
||||
|
||||
def sample_lane(self, points, sample_ys):
|
||||
# this function expects the points to be sorted
|
||||
points = np.array(points)
|
||||
if not np.all(points[1:, 1] < points[:-1, 1]):
|
||||
raise Exception('Annotaion points have to be sorted')
|
||||
x, y = points[:, 0], points[:, 1]
|
||||
|
||||
# interpolate points inside domain
|
||||
assert len(points) > 1
|
||||
interp = InterpolatedUnivariateSpline(y[::-1],
|
||||
x[::-1],
|
||||
k=min(3,
|
||||
len(points) - 1))
|
||||
domain_min_y = y.min()
|
||||
domain_max_y = y.max()
|
||||
sample_ys_inside_domain = sample_ys[(sample_ys >= domain_min_y)
|
||||
& (sample_ys <= domain_max_y)]
|
||||
assert len(sample_ys_inside_domain) > 0
|
||||
interp_xs = interp(sample_ys_inside_domain)
|
||||
|
||||
# extrapolate lane to the bottom of the image with a straight line using the 2 points closest to the bottom
|
||||
two_closest_points = points[:2]
|
||||
extrap = np.polyfit(two_closest_points[:, 1],
|
||||
two_closest_points[:, 0],
|
||||
deg=1)
|
||||
extrap_ys = sample_ys[sample_ys > domain_max_y]
|
||||
extrap_xs = np.polyval(extrap, extrap_ys)
|
||||
all_xs = np.hstack((extrap_xs, interp_xs))
|
||||
|
||||
# separate between inside and outside points
|
||||
inside_mask = (all_xs >= 0) & (all_xs < self.img_w)
|
||||
xs_inside_image = all_xs[inside_mask]
|
||||
xs_outside_image = all_xs[~inside_mask]
|
||||
|
||||
return xs_outside_image, xs_inside_image
|
||||
|
||||
def filter_lane(self, lane):
|
||||
assert lane[-1][1] <= lane[0][1]
|
||||
filtered_lane = []
|
||||
used = set()
|
||||
for p in lane:
|
||||
if p[1] not in used:
|
||||
filtered_lane.append(p)
|
||||
used.add(p[1])
|
||||
|
||||
return filtered_lane
|
||||
|
||||
def transform_annotation(self, anno, img_wh=None):
|
||||
img_w, img_h = self.img_w, self.img_h
|
||||
|
||||
old_lanes = anno['lanes']
|
||||
|
||||
# removing lanes with less than 2 points
|
||||
old_lanes = filter(lambda x: len(x) > 1, old_lanes)
|
||||
# sort lane points by Y (bottom to top of the image)
|
||||
old_lanes = [sorted(lane, key=lambda x: -x[1]) for lane in old_lanes]
|
||||
# remove points with same Y (keep first occurrence)
|
||||
old_lanes = [self.filter_lane(lane) for lane in old_lanes]
|
||||
# normalize the annotation coordinates
|
||||
old_lanes = [[[
|
||||
x * self.img_w / float(img_w), y * self.img_h / float(img_h)
|
||||
] for x, y in lane] for lane in old_lanes]
|
||||
# create tranformed annotations
|
||||
lanes = np.ones(
|
||||
(self.max_lanes, 2 + 1 + 1 + 2 + self.n_offsets), dtype=np.float32
|
||||
) * -1e5 # 2 scores, 1 start_y, 1 start_x, 1 theta, 1 length, S+1 coordinates
|
||||
lanes_endpoints = np.ones((self.max_lanes, 2))
|
||||
# lanes are invalid by default
|
||||
lanes[:, 0] = 1
|
||||
lanes[:, 1] = 0
|
||||
for lane_idx, lane in enumerate(old_lanes):
|
||||
if lane_idx >= self.max_lanes:
|
||||
break
|
||||
|
||||
try:
|
||||
xs_outside_image, xs_inside_image = self.sample_lane(
|
||||
lane, self.offsets_ys)
|
||||
except AssertionError:
|
||||
continue
|
||||
if len(xs_inside_image) <= 1:
|
||||
continue
|
||||
all_xs = np.hstack((xs_outside_image, xs_inside_image))
|
||||
lanes[lane_idx, 0] = 0
|
||||
lanes[lane_idx, 1] = 1
|
||||
lanes[lane_idx, 2] = len(xs_outside_image) / self.n_strips
|
||||
lanes[lane_idx, 3] = xs_inside_image[0]
|
||||
|
||||
thetas = []
|
||||
for i in range(1, len(xs_inside_image)):
|
||||
theta = math.atan(
|
||||
i * self.strip_size /
|
||||
(xs_inside_image[i] - xs_inside_image[0] + 1e-5)) / math.pi
|
||||
theta = theta if theta > 0 else 1 - abs(theta)
|
||||
thetas.append(theta)
|
||||
|
||||
theta_far = sum(thetas) / len(thetas)
|
||||
|
||||
# lanes[lane_idx,
|
||||
# 4] = (theta_closest + theta_far) / 2 # averaged angle
|
||||
lanes[lane_idx, 4] = theta_far
|
||||
lanes[lane_idx, 5] = len(xs_inside_image)
|
||||
lanes[lane_idx, 6:6 + len(all_xs)] = all_xs
|
||||
lanes_endpoints[lane_idx, 0] = (len(all_xs) - 1) / self.n_strips
|
||||
lanes_endpoints[lane_idx, 1] = xs_inside_image[-1]
|
||||
|
||||
new_anno = {
|
||||
'label': lanes,
|
||||
'old_anno': anno,
|
||||
'lane_endpoints': lanes_endpoints
|
||||
}
|
||||
return new_anno
|
||||
|
||||
def linestrings_to_lanes(self, lines):
|
||||
lanes = []
|
||||
for line in lines:
|
||||
lanes.append(line.coords)
|
||||
|
||||
return lanes
|
||||
|
||||
def __call__(self, sample):
|
||||
img_org = sample['img']
|
||||
line_strings_org = self.lane_to_linestrings(sample['lanes'])
|
||||
line_strings_org = LineStringsOnImage(line_strings_org,
|
||||
shape=img_org.shape)
|
||||
|
||||
for i in range(30):
|
||||
if self.training:
|
||||
mask_org = SegmentationMapsOnImage(sample['mask'],
|
||||
shape=img_org.shape)
|
||||
img, line_strings, seg = self.transform(
|
||||
image=img_org.copy().astype(np.uint8),
|
||||
line_strings=line_strings_org,
|
||||
segmentation_maps=mask_org)
|
||||
else:
|
||||
img, line_strings = self.transform(
|
||||
image=img_org.copy().astype(np.uint8),
|
||||
line_strings=line_strings_org)
|
||||
line_strings.clip_out_of_image_()
|
||||
new_anno = {'lanes': self.linestrings_to_lanes(line_strings)}
|
||||
try:
|
||||
annos = self.transform_annotation(new_anno,
|
||||
img_wh=(self.img_w,
|
||||
self.img_h))
|
||||
label = annos['label']
|
||||
lane_endpoints = annos['lane_endpoints']
|
||||
break
|
||||
except:
|
||||
if (i + 1) == 30:
|
||||
self.logger.critical(
|
||||
'Transform annotation failed 30 times :(')
|
||||
exit()
|
||||
|
||||
sample['img'] = img.astype(np.float32) / 255.
|
||||
sample['lane_line'] = label
|
||||
sample['lanes_endpoints'] = lane_endpoints
|
||||
sample['gt_points'] = new_anno['lanes']
|
||||
sample['seg'] = seg.get_arr() if self.training else np.zeros(
|
||||
img_org.shape)
|
||||
|
||||
return sample
|
||||
@@ -0,0 +1,48 @@
|
||||
import collections
|
||||
|
||||
from clrnet.utils import build_from_cfg
|
||||
|
||||
from ..registry import PROCESS
|
||||
|
||||
|
||||
class Process(object):
|
||||
"""Compose multiple process sequentially.
|
||||
Args:
|
||||
process (Sequence[dict | callable]): Sequence of process object or
|
||||
config dict to be composed.
|
||||
"""
|
||||
def __init__(self, processes, cfg):
|
||||
assert isinstance(processes, collections.abc.Sequence)
|
||||
self.processes = []
|
||||
for process in processes:
|
||||
if isinstance(process, dict):
|
||||
process = build_from_cfg(process,
|
||||
PROCESS,
|
||||
default_args=dict(cfg=cfg))
|
||||
self.processes.append(process)
|
||||
elif callable(process):
|
||||
self.processes.append(process)
|
||||
else:
|
||||
raise TypeError('process must be callable or a dict')
|
||||
|
||||
def __call__(self, data):
|
||||
"""Call function to apply processes sequentially.
|
||||
Args:
|
||||
data (dict): A result dict contains the data to process.
|
||||
Returns:
|
||||
dict: Processed data.
|
||||
"""
|
||||
|
||||
for t in self.processes:
|
||||
data = t(data)
|
||||
if data is None:
|
||||
return None
|
||||
return data
|
||||
|
||||
def __repr__(self):
|
||||
format_string = self.__class__.__name__ + '('
|
||||
for t in self.processes:
|
||||
format_string += '\n'
|
||||
format_string += f' {t}'
|
||||
format_string += '\n)'
|
||||
return format_string
|
||||
@@ -0,0 +1,311 @@
|
||||
import random
|
||||
import cv2
|
||||
import numpy as np
|
||||
import torch
|
||||
import numbers
|
||||
import collections
|
||||
from PIL import Image
|
||||
|
||||
from ..registry import PROCESS
|
||||
|
||||
|
||||
def to_tensor(data):
|
||||
"""Convert objects of various python types to :obj:`torch.Tensor`.
|
||||
|
||||
Supported types are: :class:`numpy.ndarray`, :class:`torch.Tensor`,
|
||||
:class:`Sequence`, :class:`int` and :class:`float`.
|
||||
|
||||
Args:
|
||||
data (torch.Tensor | numpy.ndarray | Sequence | int | float): Data to
|
||||
be converted.
|
||||
"""
|
||||
|
||||
if isinstance(data, torch.Tensor):
|
||||
return data
|
||||
elif isinstance(data, np.ndarray):
|
||||
return torch.from_numpy(data)
|
||||
elif isinstance(data, int):
|
||||
return torch.LongTensor([data])
|
||||
elif isinstance(data, float):
|
||||
return torch.FloatTensor([data])
|
||||
else:
|
||||
raise TypeError(f'type {type(data)} cannot be converted to tensor.')
|
||||
|
||||
|
||||
@PROCESS.register_module
|
||||
class ToTensor(object):
|
||||
"""Convert some results to :obj:`torch.Tensor` by given keys.
|
||||
|
||||
Args:
|
||||
keys (Sequence[str]): Keys that need to be converted to Tensor.
|
||||
"""
|
||||
def __init__(self, keys=['img', 'mask'], cfg=None):
|
||||
self.keys = keys
|
||||
|
||||
def __call__(self, sample):
|
||||
data = {}
|
||||
if len(sample['img'].shape) < 3:
|
||||
sample['img'] = np.expand_dims(img, -1)
|
||||
for key in self.keys:
|
||||
if key == 'img_metas' or key == 'gt_masks' or key == 'lane_line':
|
||||
data[key] = sample[key]
|
||||
continue
|
||||
data[key] = to_tensor(sample[key])
|
||||
data['img'] = data['img'].permute(2, 0, 1)
|
||||
return data
|
||||
|
||||
def __repr__(self):
|
||||
return self.__class__.__name__ + f'(keys={self.keys})'
|
||||
|
||||
|
||||
@PROCESS.register_module
|
||||
class RandomLROffsetLABEL(object):
|
||||
def __init__(self, max_offset, cfg=None):
|
||||
self.max_offset = max_offset
|
||||
|
||||
def __call__(self, sample):
|
||||
img = sample['img']
|
||||
label = sample['mask']
|
||||
offset = np.random.randint(-self.max_offset, self.max_offset)
|
||||
h, w = img.shape[:2]
|
||||
|
||||
img = np.array(img)
|
||||
if offset > 0:
|
||||
img[:, offset:, :] = img[:, 0:w - offset, :]
|
||||
img[:, :offset, :] = 0
|
||||
if offset < 0:
|
||||
real_offset = -offset
|
||||
img[:, 0:w - real_offset, :] = img[:, real_offset:, :]
|
||||
img[:, w - real_offset:, :] = 0
|
||||
|
||||
label = np.array(label)
|
||||
if offset > 0:
|
||||
label[:, offset:] = label[:, 0:w - offset]
|
||||
label[:, :offset] = 0
|
||||
if offset < 0:
|
||||
offset = -offset
|
||||
label[:, 0:w - offset] = label[:, offset:]
|
||||
label[:, w - offset:] = 0
|
||||
sample['img'] = img
|
||||
sample['mask'] = label
|
||||
|
||||
return sample
|
||||
|
||||
|
||||
@PROCESS.register_module
|
||||
class RandomUDoffsetLABEL(object):
|
||||
def __init__(self, max_offset, cfg=None):
|
||||
self.max_offset = max_offset
|
||||
|
||||
def __call__(self, sample):
|
||||
img = sample['img']
|
||||
label = sample['mask']
|
||||
offset = np.random.randint(-self.max_offset, self.max_offset)
|
||||
h, w = img.shape[:2]
|
||||
|
||||
img = np.array(img)
|
||||
if offset > 0:
|
||||
img[offset:, :, :] = img[0:h - offset, :, :]
|
||||
img[:offset, :, :] = 0
|
||||
if offset < 0:
|
||||
real_offset = -offset
|
||||
img[0:h - real_offset, :, :] = img[real_offset:, :, :]
|
||||
img[h - real_offset:, :, :] = 0
|
||||
|
||||
label = np.array(label)
|
||||
if offset > 0:
|
||||
label[offset:, :] = label[0:h - offset, :]
|
||||
label[:offset, :] = 0
|
||||
if offset < 0:
|
||||
offset = -offset
|
||||
label[0:h - offset, :] = label[offset:, :]
|
||||
label[h - offset:, :] = 0
|
||||
sample['img'] = img
|
||||
sample['mask'] = label
|
||||
return sample
|
||||
|
||||
|
||||
@PROCESS.register_module
|
||||
class Resize(object):
|
||||
def __init__(self, size, cfg=None):
|
||||
assert (isinstance(size, collections.Iterable) and len(size) == 2)
|
||||
self.size = size
|
||||
|
||||
def __call__(self, sample):
|
||||
out = list()
|
||||
sample['img'] = cv2.resize(sample['img'],
|
||||
self.size,
|
||||
interpolation=cv2.INTER_CUBIC)
|
||||
if 'mask' in sample:
|
||||
sample['mask'] = cv2.resize(sample['mask'],
|
||||
self.size,
|
||||
interpolation=cv2.INTER_NEAREST)
|
||||
return sample
|
||||
|
||||
|
||||
@PROCESS.register_module
|
||||
class RandomCrop(object):
|
||||
def __init__(self, size, cfg=None):
|
||||
if isinstance(size, numbers.Number):
|
||||
self.size = (int(size), int(size))
|
||||
else:
|
||||
self.size = size
|
||||
|
||||
def __call__(self, img_group):
|
||||
h, w = img_group[0].shape[0:2]
|
||||
th, tw = self.size
|
||||
|
||||
out_images = list()
|
||||
h1 = random.randint(0, max(0, h - th))
|
||||
w1 = random.randint(0, max(0, w - tw))
|
||||
h2 = min(h1 + th, h)
|
||||
w2 = min(w1 + tw, w)
|
||||
|
||||
for img in img_group:
|
||||
assert (img.shape[0] == h and img.shape[1] == w)
|
||||
out_images.append(img[h1:h2, w1:w2, ...])
|
||||
return out_images
|
||||
|
||||
|
||||
@PROCESS.register_module
|
||||
class CenterCrop(object):
|
||||
def __init__(self, size, cfg=None):
|
||||
if isinstance(size, numbers.Number):
|
||||
self.size = (int(size), int(size))
|
||||
else:
|
||||
self.size = size
|
||||
|
||||
def __call__(self, img_group):
|
||||
h, w = img_group[0].shape[0:2]
|
||||
th, tw = self.size
|
||||
|
||||
out_images = list()
|
||||
h1 = max(0, int((h - th) / 2))
|
||||
w1 = max(0, int((w - tw) / 2))
|
||||
h2 = min(h1 + th, h)
|
||||
w2 = min(w1 + tw, w)
|
||||
|
||||
for img in img_group:
|
||||
assert (img.shape[0] == h and img.shape[1] == w)
|
||||
out_images.append(img[h1:h2, w1:w2, ...])
|
||||
return out_images
|
||||
|
||||
|
||||
@PROCESS.register_module
|
||||
class RandomRotation(object):
|
||||
def __init__(self,
|
||||
degree=(-10, 10),
|
||||
interpolation=(cv2.INTER_LINEAR, cv2.INTER_NEAREST),
|
||||
padding=None,
|
||||
cfg=None):
|
||||
self.degree = degree
|
||||
self.interpolation = interpolation
|
||||
self.padding = padding
|
||||
if self.padding is None:
|
||||
self.padding = [0, 0]
|
||||
|
||||
def _rotate_img(self, sample, map_matrix):
|
||||
h, w = sample['img'].shape[0:2]
|
||||
sample['img'] = cv2.warpAffine(sample['img'],
|
||||
map_matrix, (w, h),
|
||||
flags=cv2.INTER_LINEAR,
|
||||
borderMode=cv2.BORDER_CONSTANT,
|
||||
borderValue=self.padding)
|
||||
|
||||
def _rotate_mask(self, sample, map_matrix):
|
||||
if 'mask' not in sample:
|
||||
return
|
||||
h, w = sample['mask'].shape[0:2]
|
||||
sample['mask'] = cv2.warpAffine(sample['mask'],
|
||||
map_matrix, (w, h),
|
||||
flags=cv2.INTER_NEAREST,
|
||||
borderMode=cv2.BORDER_CONSTANT,
|
||||
borderValue=self.padding)
|
||||
|
||||
def __call__(self, sample):
|
||||
v = random.random()
|
||||
if v < 0.5:
|
||||
degree = random.uniform(self.degree[0], self.degree[1])
|
||||
h, w = sample['img'].shape[0:2]
|
||||
center = (w / 2, h / 2)
|
||||
map_matrix = cv2.getRotationMatrix2D(center, degree, 1.0)
|
||||
self._rotate_img(sample, map_matrix)
|
||||
self._rotate_mask(sample, map_matrix)
|
||||
return sample
|
||||
|
||||
|
||||
@PROCESS.register_module
|
||||
class RandomBlur(object):
|
||||
def __init__(self, applied, cfg=None):
|
||||
self.applied = applied
|
||||
|
||||
def __call__(self, img_group):
|
||||
assert (len(self.applied) == len(img_group))
|
||||
v = random.random()
|
||||
if v < 0.5:
|
||||
out_images = []
|
||||
for img, a in zip(img_group, self.applied):
|
||||
if a:
|
||||
img = cv2.GaussianBlur(img, (5, 5),
|
||||
random.uniform(1e-6, 0.6))
|
||||
out_images.append(img)
|
||||
if len(img.shape) > len(out_images[-1].shape):
|
||||
out_images[-1] = out_images[-1][
|
||||
..., np.newaxis] # single channel image
|
||||
return out_images
|
||||
else:
|
||||
return img_group
|
||||
|
||||
|
||||
@PROCESS.register_module
|
||||
class RandomHorizontalFlip(object):
|
||||
"""Randomly horizontally flips the given numpy Image with a probability of 0.5
|
||||
"""
|
||||
def __init__(self, cfg=None):
|
||||
pass
|
||||
|
||||
def __call__(self, sample):
|
||||
v = random.random()
|
||||
if v < 0.5:
|
||||
sample['img'] = np.fliplr(sample['img'])
|
||||
if 'mask' in sample: sample['mask'] = np.fliplr(sample['mask'])
|
||||
return sample
|
||||
|
||||
|
||||
@PROCESS.register_module
|
||||
class Normalize(object):
|
||||
def __init__(self, img_norm, cfg=None):
|
||||
self.mean = np.array(img_norm['mean'], dtype=np.float32)
|
||||
self.std = np.array(img_norm['std'], dtype=np.float32)
|
||||
|
||||
def __call__(self, sample):
|
||||
m = self.mean
|
||||
s = self.std
|
||||
img = sample['img']
|
||||
if len(m) == 1:
|
||||
img = img - np.array(m) # single channel image
|
||||
img = img / np.array(s)
|
||||
else:
|
||||
img = img - np.array(m)[np.newaxis, np.newaxis, ...]
|
||||
img = img / np.array(s)[np.newaxis, np.newaxis, ...]
|
||||
sample['img'] = img
|
||||
|
||||
return sample
|
||||
|
||||
|
||||
def CLRTransforms(img_h, img_w):
|
||||
return [
|
||||
dict(name='Resize',
|
||||
parameters=dict(size=dict(height=img_h, width=img_w)),
|
||||
p=1.0),
|
||||
dict(name='HorizontalFlip', parameters=dict(p=1.0), p=0.5),
|
||||
dict(name='Affine',
|
||||
parameters=dict(translate_percent=dict(x=(-0.1, 0.1),
|
||||
y=(-0.1, 0.1)),
|
||||
rotate=(-10, 10),
|
||||
scale=(0.8, 1.2)),
|
||||
p=0.7),
|
||||
dict(name='Resize',
|
||||
parameters=dict(size=dict(height=img_h, width=img_w)),
|
||||
p=1.0),
|
||||
]
|
||||
Reference in New Issue
Block a user