| | """ |
| | Data augmentation utilities for signature verification training. |
| | """ |
| |
|
| | import torch |
| | import numpy as np |
| | from typing import Tuple, List, Union |
| | import albumentations as A |
| | from albumentations.pytorch import ToTensorV2 |
| |
|
| |
|
| | class SignatureAugmentationPipeline: |
| | """ |
| | Comprehensive augmentation pipeline for signature verification. |
| | """ |
| | |
| | def __init__(self, |
| | target_size: Tuple[int, int] = (224, 224), |
| | augmentation_strength: str = 'medium'): |
| | """ |
| | Initialize augmentation pipeline. |
| | |
| | Args: |
| | target_size: Target size for signature images |
| | augmentation_strength: 'light', 'medium', or 'heavy' |
| | """ |
| | self.target_size = target_size |
| | self.strength = augmentation_strength |
| | |
| | |
| | self._setup_augmentations() |
| | |
| | def _setup_augmentations(self): |
| | """Setup augmentation transforms based on strength.""" |
| | |
| | if self.strength == 'light': |
| | self.train_transform = A.Compose([ |
| | A.Resize(self.target_size[0], self.target_size[1]), |
| | A.HorizontalFlip(p=0.2), |
| | A.Rotate(limit=5, p=0.3), |
| | A.RandomBrightnessContrast( |
| | brightness_limit=0.1, |
| | contrast_limit=0.1, |
| | p=0.3 |
| | ), |
| | A.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), |
| | ToTensorV2() |
| | ]) |
| | |
| | elif self.strength == 'medium': |
| | self.train_transform = A.Compose([ |
| | A.Resize(self.target_size[0], self.target_size[1]), |
| | A.HorizontalFlip(p=0.3), |
| | A.Rotate(limit=10, p=0.4), |
| | A.RandomBrightnessContrast( |
| | brightness_limit=0.15, |
| | contrast_limit=0.15, |
| | p=0.4 |
| | ), |
| | A.GaussNoise(var_limit=(5.0, 25.0), p=0.2), |
| | A.ElasticTransform( |
| | alpha=0.5, |
| | sigma=25, |
| | alpha_affine=25, |
| | p=0.2 |
| | ), |
| | A.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), |
| | ToTensorV2() |
| | ]) |
| | |
| | else: |
| | self.train_transform = A.Compose([ |
| | A.Resize(self.target_size[0], self.target_size[1]), |
| | A.HorizontalFlip(p=0.4), |
| | A.Rotate(limit=15, p=0.5), |
| | A.RandomBrightnessContrast( |
| | brightness_limit=0.2, |
| | contrast_limit=0.2, |
| | p=0.5 |
| | ), |
| | A.GaussNoise(var_limit=(10.0, 50.0), p=0.3), |
| | A.ElasticTransform( |
| | alpha=1, |
| | sigma=50, |
| | alpha_affine=50, |
| | p=0.3 |
| | ), |
| | A.Perspective(scale=(0.05, 0.1), p=0.2), |
| | A.ShiftScaleRotate( |
| | shift_limit=0.05, |
| | scale_limit=0.1, |
| | rotate_limit=10, |
| | p=0.3 |
| | ), |
| | A.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), |
| | ToTensorV2() |
| | ]) |
| | |
| | |
| | self.val_transform = A.Compose([ |
| | A.Resize(self.target_size[0], self.target_size[1]), |
| | A.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), |
| | ToTensorV2() |
| | ]) |
| | |
| | def augment_image(self, image: np.ndarray, is_training: bool = True) -> torch.Tensor: |
| | """ |
| | Apply augmentation to a single image. |
| | |
| | Args: |
| | image: Input signature image |
| | is_training: Whether to apply training augmentations |
| | |
| | Returns: |
| | Augmented image as torch tensor |
| | """ |
| | transform = self.train_transform if is_training else self.val_transform |
| | transformed = transform(image=image) |
| | return transformed['image'] |
| | |
| | def augment_batch(self, images: List[np.ndarray], is_training: bool = True) -> torch.Tensor: |
| | """ |
| | Apply augmentation to a batch of images. |
| | |
| | Args: |
| | images: List of images to augment |
| | is_training: Whether to apply training augmentations |
| | |
| | Returns: |
| | Batch of augmented images as torch tensor |
| | """ |
| | augmented_images = [] |
| | for image in images: |
| | augmented = self.augment_image(image, is_training) |
| | augmented_images.append(augmented) |
| | |
| | return torch.stack(augmented_images) |
| |
|
| |
|
| | class PairAugmentation: |
| | """ |
| | Specialized augmentation for signature pairs in Siamese networks. |
| | """ |
| | |
| | def __init__(self, target_size: Tuple[int, int] = (224, 224)): |
| | """ |
| | Initialize pair augmentation. |
| | |
| | Args: |
| | target_size: Target size for signature images |
| | """ |
| | self.target_size = target_size |
| | |
| | |
| | self.shared_transform = A.Compose([ |
| | A.Resize(self.target_size[0], self.target_size[1]), |
| | A.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), |
| | ToTensorV2() |
| | ]) |
| | |
| | |
| | self.individual_transform = A.Compose([ |
| | A.HorizontalFlip(p=0.3), |
| | A.Rotate(limit=10, p=0.4), |
| | A.RandomBrightnessContrast( |
| | brightness_limit=0.15, |
| | contrast_limit=0.15, |
| | p=0.4 |
| | ), |
| | A.GaussNoise(var_limit=(5.0, 25.0), p=0.2), |
| | ]) |
| | |
| | def augment_pair(self, |
| | signature1: np.ndarray, |
| | signature2: np.ndarray, |
| | is_training: bool = True) -> Tuple[torch.Tensor, torch.Tensor]: |
| | """ |
| | Augment a pair of signatures. |
| | |
| | Args: |
| | signature1: First signature image |
| | signature2: Second signature image |
| | is_training: Whether to apply training augmentations |
| | |
| | Returns: |
| | Tuple of augmented signature tensors |
| | """ |
| | if is_training: |
| | |
| | aug1 = self.individual_transform(image=signature1) |
| | aug2 = self.individual_transform(image=signature2) |
| | |
| | |
| | final1 = self.shared_transform(image=aug1['image']) |
| | final2 = self.shared_transform(image=aug2['image']) |
| | else: |
| | |
| | final1 = self.shared_transform(image=signature1) |
| | final2 = self.shared_transform(image=signature2) |
| | |
| | return final1['image'], final2['image'] |
| |
|
| |
|
| | class OnlineAugmentation: |
| | """ |
| | Online augmentation during training for dynamic augmentation. |
| | """ |
| | |
| | def __init__(self, target_size: Tuple[int, int] = (224, 224)): |
| | """ |
| | Initialize online augmentation. |
| | |
| | Args: |
| | target_size: Target size for signature images |
| | """ |
| | self.target_size = target_size |
| | self.augmentation_pipeline = SignatureAugmentationPipeline( |
| | target_size=target_size, |
| | augmentation_strength='medium' |
| | ) |
| | |
| | def __call__(self, image: np.ndarray, is_training: bool = True) -> torch.Tensor: |
| | """ |
| | Apply online augmentation. |
| | |
| | Args: |
| | image: Input signature image |
| | is_training: Whether to apply training augmentations |
| | |
| | Returns: |
| | Augmented image as torch tensor |
| | """ |
| | return self.augmentation_pipeline.augment_image(image, is_training) |
| | |
| | def set_strength(self, strength: str): |
| | """ |
| | Dynamically change augmentation strength. |
| | |
| | Args: |
| | strength: 'light', 'medium', or 'heavy' |
| | """ |
| | self.augmentation_pipeline = SignatureAugmentationPipeline( |
| | target_size=self.target_size, |
| | augmentation_strength=strength |
| | ) |
| |
|