263 lines
6.9 KiB
Python
263 lines
6.9 KiB
Python
"""
|
|
Tahap 2: Normalize Landmark
|
|
|
|
Script ini menormalisasi landmark yang sudah diekstrak
|
|
dengan translation dan scaling untuk konsistensi.
|
|
|
|
Sesuai README:
|
|
- Input: dataset/landmark/
|
|
- Output: dataset/normalized/
|
|
- Proses:
|
|
- Membaca seluruh landmark
|
|
- Menggeser koordinat ke titik referensi
|
|
- Scaling
|
|
- Menyimpan landmark hasil normalisasi
|
|
|
|
Catatan: Struktur folder pada normalized/ akan dibuat identik dengan folder landmark/ dan raw/.
|
|
"""
|
|
|
|
import sys
|
|
import numpy as np
|
|
from pathlib import Path
|
|
from tqdm import tqdm
|
|
from typing import List, Tuple, Optional
|
|
import argparse
|
|
|
|
# Add parent directory to path
|
|
sys.path.insert(0, str(Path(__file__).parent.parent))
|
|
|
|
from utils.normalization import LandmarkNormalizer
|
|
from utils.config import (
|
|
LANDMARK_DIR,
|
|
NORMALIZED_DIR,
|
|
CATEGORIES,
|
|
WINDOW_SIZE,
|
|
FEATURE_SIZE
|
|
)
|
|
|
|
|
|
class LandmarkNormalizerPipeline:
|
|
"""
|
|
Pipeline untuk normalisasi landmark sequences.
|
|
"""
|
|
|
|
def __init__(self, scale_factor: Optional[float] = None):
|
|
"""
|
|
Inisialisasi normalizer pipeline.
|
|
|
|
Args:
|
|
scale_factor: Optional fixed scale factor
|
|
"""
|
|
self.normalizer = LandmarkNormalizer(scale_factor=scale_factor)
|
|
|
|
def normalize_sequence(self, landmarks: np.ndarray) -> np.ndarray:
|
|
"""
|
|
Normalisasi satu sequence landmark.
|
|
|
|
Args:
|
|
landmarks: Array shape (30, 306)
|
|
|
|
Returns:
|
|
Normalized sequence
|
|
"""
|
|
if landmarks is None or len(landmarks) == 0:
|
|
return np.zeros((WINDOW_SIZE, FEATURE_SIZE))
|
|
|
|
normalized_sequence = []
|
|
|
|
for frame_features in landmarks:
|
|
# Split frame ke components
|
|
left_hand = frame_features[:63].reshape(21, 3)
|
|
right_hand = frame_features[63:126].reshape(21, 3)
|
|
face = frame_features[126:].reshape(60, 3)
|
|
|
|
# Normalize
|
|
norm_frame = self.normalizer.normalize_frame(left_hand, right_hand, face)
|
|
normalized_sequence.append(norm_frame)
|
|
|
|
return np.array(normalized_sequence)
|
|
|
|
|
|
def get_landmark_files(directory: Path) -> List[Path]:
|
|
"""
|
|
Get semua landmark .npy files dalam directory.
|
|
|
|
Args:
|
|
directory: Root directory
|
|
|
|
Returns:
|
|
List of .npy file paths
|
|
"""
|
|
if not directory.exists():
|
|
return []
|
|
|
|
return sorted(directory.rglob("*.npy"))
|
|
|
|
|
|
def normalize_category(
|
|
landmark_dir: Path,
|
|
normalized_dir: Path,
|
|
category: str
|
|
) -> Tuple[int, int]:
|
|
"""
|
|
Normalisasi landmarks untuk satu kategori.
|
|
|
|
Args:
|
|
landmark_dir: Directory landmark mentah
|
|
normalized_dir: Directory output normalized
|
|
category: Nama kategori
|
|
|
|
Returns:
|
|
Tuple (success_count, fail_count)
|
|
"""
|
|
category_landmark_dir = landmark_dir / category
|
|
|
|
if not category_landmark_dir.exists():
|
|
print(f"Warning: Category directory tidak ditemukan: {category_landmark_dir}")
|
|
return 0, 0
|
|
|
|
# Get all landmark files
|
|
landmark_files = get_landmark_files(category_landmark_dir)
|
|
|
|
if not landmark_files:
|
|
print(f"Warning: Tidak ada landmark file ditemukan untuk kategori {category}")
|
|
return 0, 0
|
|
|
|
print(f"\n{'='*60}")
|
|
print(f"Normalizing landmarks untuk kategori: {category}")
|
|
print(f"Total files: {len(landmark_files)}")
|
|
print(f"{'='*60}\n")
|
|
|
|
# Initialize normalizer
|
|
normalizer = LandmarkNormalizerPipeline()
|
|
|
|
success_count = 0
|
|
fail_count = 0
|
|
|
|
for landmark_path in tqdm(landmark_files, desc=f"Normalizing {category}"):
|
|
try:
|
|
# Load landmark
|
|
sequence = np.load(landmark_path)
|
|
|
|
# Get label dari path
|
|
# Format: landmark/{category}/{label}/file.npy
|
|
relative_path = landmark_path.relative_to(category_landmark_dir)
|
|
parts = relative_path.parts
|
|
|
|
if len(parts) >= 2:
|
|
label = parts[0] # Folder label
|
|
|
|
# Create output path (struktur identik)
|
|
output_dir = normalized_dir / category / label
|
|
output_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
output_path = output_dir / landmark_path.name
|
|
|
|
# Skip jika sudah ada
|
|
if output_path.exists():
|
|
continue
|
|
|
|
# Normalize
|
|
normalized_sequence = normalizer.normalize_sequence(sequence)
|
|
|
|
# Save
|
|
np.save(output_path, normalized_sequence)
|
|
success_count += 1
|
|
else:
|
|
fail_count += 1
|
|
|
|
except Exception as e:
|
|
print(f"Error processing {landmark_path}: {e}")
|
|
fail_count += 1
|
|
|
|
return success_count, fail_count
|
|
|
|
|
|
def normalize_all_categories(
|
|
landmark_dir: Path = LANDMARK_DIR,
|
|
normalized_dir: Path = NORMALIZED_DIR,
|
|
categories: List[str] = None
|
|
):
|
|
"""
|
|
Normalisasi landmarks untuk semua kategori.
|
|
|
|
Args:
|
|
landmark_dir: Directory landmark mentah
|
|
normalized_dir: Directory output normalized
|
|
categories: List kategori yang akan diproses
|
|
"""
|
|
if categories is None:
|
|
categories = CATEGORIES
|
|
|
|
print("="*60)
|
|
print("TAHAP 2: NORMALIZE LANDMARK")
|
|
print("="*60)
|
|
print(f"Input: {landmark_dir}")
|
|
print(f"Output: {normalized_dir}")
|
|
print(f"Categories: {categories}")
|
|
print("="*60)
|
|
|
|
total_success = 0
|
|
total_fail = 0
|
|
|
|
for category in categories:
|
|
success, fail = normalize_category(
|
|
landmark_dir=landmark_dir,
|
|
normalized_dir=normalized_dir,
|
|
category=category
|
|
)
|
|
total_success += success
|
|
total_fail += fail
|
|
|
|
print("\n" + "="*60)
|
|
print("NORMALIZATION COMPLETE")
|
|
print("="*60)
|
|
print(f"Total berhasil: {total_success}")
|
|
print(f"Total gagal: {total_fail}")
|
|
if total_success + total_fail > 0:
|
|
print(f"Success rate: {total_success/(total_success+total_fail)*100:.2f}%")
|
|
print("="*60)
|
|
|
|
|
|
def main():
|
|
"""Main entry point."""
|
|
parser = argparse.ArgumentParser(
|
|
description="Normalisasi landmark untuk gesture recognition"
|
|
)
|
|
parser.add_argument(
|
|
"--input",
|
|
type=str,
|
|
default=None,
|
|
help="Directory landmark (default: dataset/landmark)"
|
|
)
|
|
parser.add_argument(
|
|
"--output",
|
|
type=str,
|
|
default=None,
|
|
help="Directory output normalized (default: dataset/normalized)"
|
|
)
|
|
parser.add_argument(
|
|
"--categories",
|
|
nargs="+",
|
|
choices=["abjad_bisindo", "abjad_sibi", "angka", "kata"],
|
|
default=None,
|
|
help="Kategori yang akan diproses"
|
|
)
|
|
|
|
args = parser.parse_args()
|
|
|
|
# Setup paths
|
|
landmark_dir = Path(args.input) if args.input else LANDMARK_DIR
|
|
normalized_dir = Path(args.output) if args.output else NORMALIZED_DIR
|
|
|
|
# Normalize
|
|
normalize_all_categories(
|
|
landmark_dir=landmark_dir,
|
|
normalized_dir=normalized_dir,
|
|
categories=args.categories
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|