diff --git a/.gitignore b/.gitignore index 1053134..dfc0fcf 100644 --- a/.gitignore +++ b/.gitignore @@ -49,4 +49,21 @@ po/*~ rsconnect/ #DS Store -.DS_Store \ No newline at end of file +.DS_Store + +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +env/ +venv/ +ENV/ +.venv +pip-log.txt +pip-delete-this-directory.txt +.pytest_cache/ +*.egg-info/ +dist/ +build/ \ No newline at end of file diff --git a/code/count-automation/README.md b/code/count-automation/README.md new file mode 100644 index 0000000..a807aff --- /dev/null +++ b/code/count-automation/README.md @@ -0,0 +1,431 @@ +# Oyster Count Automation + +A machine learning pipeline for automatically counting oysters in photographs using computer vision techniques. + +## Overview + +This system uses adaptive thresholding, morphological operations, and contour detection to identify and count individual oysters in high-resolution images. The algorithm has been optimized on a set of 8 labeled images containing between 99-116 oysters each. + +## Directory Structure + +``` +count-automation/ +├── README.md # This file +├── requirements.txt # Python dependencies +├── oyster_counter.py # Main counting algorithm +├── optimize_parameters.py # Parameter optimization script +├── batch_process.py # Batch processing utility +└── example_usage.py # Example code demonstrating usage +``` + +## Installation + +### Prerequisites + +- Python 3.8 or higher +- pip package manager + +### Setup + +1. Install required Python packages: + +```bash +pip install -r requirements.txt +``` + +Required packages: +- opencv-python-headless >= 4.8.0 +- numpy >= 1.24.0 +- scikit-image >= 0.21.0 +- matplotlib >= 3.7.0 +- pandas >= 2.0.0 +- pillow >= 10.0.0 + +## Quick Start + +For a complete set of examples, run: +```bash +python example_usage.py +``` + +This will demonstrate: +- Counting oysters with default parameters +- Using custom parameters +- Loading optimized parameters from file +- Programmatic evaluation +- Parameter experimentation + +## Usage + +### Basic Usage - Count Oysters in a Single Image + +```bash +python oyster_counter.py --input /path/to/image.jpeg --output /path/to/output_dir +``` + +### Evaluate on a Dataset of Labeled Images + +```bash +python oyster_counter.py --input /path/to/image_directory --evaluate --output /path/to/output_dir +``` + +The filenames should follow the convention: `prefix-COUNT.jpeg` where COUNT is the actual number of oysters. + +### Using Custom Parameters + +```bash +python oyster_counter.py --input /path/to/image.jpeg --params custom_params.json +``` + +### Parameter Optimization + +To optimize parameters on your labeled dataset: + +```bash +python optimize_parameters.py # Quick optimization (96 combinations) +python optimize_parameters.py --full # Full optimization (1728 combinations) +``` + +## Algorithm Description + +### Pipeline Overview + +1. **Image Preprocessing** + - Resize image by 0.25x for faster processing (configurable) + - Convert to grayscale + - Apply Gaussian blur to reduce noise + +2. **Oyster Detection** + - Adaptive thresholding to handle varying lighting conditions + - Morphological opening to remove small noise + - Morphological closing to fill small holes + - Contour detection to identify potential oysters + +3. **Filtering and Counting** + - Filter contours by area (min/max thresholds) + - Calculate circularity: 4π × area / perimeter² + - Filter by circularity threshold + - Check aspect ratio (width/height) + - Count valid contours as oysters + +### Key Parameters + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `resize_factor` | 0.25 | Scale factor for image resizing | +| `blur_kernel` | 5 | Gaussian blur kernel size | +| `adaptive_block_size` | 75* | Block size for adaptive thresholding | +| `adaptive_c` | 8* | Constant subtracted in adaptive threshold | +| `morph_kernel_size` | 7* | Morphological operation kernel size | +| `min_area` | 300* | Minimum contour area (pixels) | +| `max_area` | 40000* | Maximum contour area (pixels) | +| `circularity_threshold` | 0.25* | Minimum circularity (0-1) | +| `aspect_ratio_max` | 3.0 | Maximum width/height ratio | + +*Optimized values based on current dataset + +## Performance Metrics + +### Current Results (8 images, 99-116 oysters each) + +- **Mean Absolute Error**: 40.6 oysters +- **Mean Percentage Error**: 38.5% + +### Detailed Results by Image + +| Image | Ground Truth | Predicted | Error | % Error | +|-------|-------------|-----------|-------|---------| +| juvenile-102.jpeg | 102 | 72 | -30 | 29.4% | +| juvenile-103.jpeg | 103 | 53 | -50 | 48.5% | +| juvenile-106.jpeg | 106 | 60 | -46 | 43.4% | +| juvenile-114.jpeg | 114 | 76 | -38 | 33.3% | +| juvenile-116.jpeg | 116 | 71 | -45 | 38.8% | +| juvenile-99.jpeg | 99 | 66 | -33 | 33.3% | +| juvenile19-106.jpeg | 106 | 67 | -39 | 36.8% | +| juvenile20-99.jpeg | 99 | 55 | -44 | 44.4% | + +### Analysis + +The algorithm currently shows: +- Consistent undercounting (negative errors) +- Better performance on some images (29-36% error) +- More challenging cases with higher density (44-48% error) +- Average detection rate: ~61.5% of actual oysters + +## Improvement Recommendations + +### Short-term Improvements (Current Dataset) + +1. **Fine-tune Detection Parameters** + - Further optimize `min_area` and `max_area` thresholds + - Adjust `circularity_threshold` for better shape matching + - Experiment with different morphological kernel sizes + +2. **Enhanced Preprocessing** + - Test different color spaces (HSV, LAB) for better segmentation + - Apply CLAHE (Contrast Limited Adaptive Histogram Equalization) + - Experiment with bilateral filtering for edge-preserving smoothing + +3. **Advanced Detection Methods** + - Implement watershed segmentation for touching oysters + - Use distance transform to separate clustered objects + - Apply connected component analysis with stricter criteria + +### Long-term Improvements (More Data) + +1. **Expand Training Dataset** + - **Priority**: Add 20-30 more images with known counts + - Include images with varying conditions: + - Different lighting (bright sun, overcast, shadows) + - Different oyster densities (sparse: 20-50, medium: 50-100, dense: 100+) + - Different backgrounds and surfaces + - Various oyster sizes (juvenile vs. adult) + - Different camera angles (overhead, angled) + +2. **Implement Deep Learning** + - With 30+ labeled images, train a custom object detection model: + - YOLO (You Only Look Once) - Fast, real-time detection + - Faster R-CNN - Higher accuracy, slower processing + - EfficientDet - Balance of speed and accuracy + - Use transfer learning from pre-trained models + - Implement data augmentation (rotation, flip, brightness, crop) + +3. **Develop Active Learning Pipeline** + - Automatically identify images where model is uncertain + - Prioritize these images for manual labeling + - Incrementally retrain model with new labels + +4. **Create Annotation Tool** + - Build simple web interface for labeling oysters + - Export labels in COCO or Pascal VOC format + - Track inter-annotator agreement + +5. **Ensemble Methods** + - Combine multiple detection algorithms + - Use voting or weighted averaging for final count + - Improve robustness across different conditions + +### Recommended Data Collection Strategy + +**Phase 1** (10 additional images): +- 5 images with 50-80 oysters (medium density) +- 5 images with 120-150 oysters (high density) + +**Phase 2** (10 additional images): +- 5 images in different lighting conditions +- 5 images with different backgrounds/surfaces + +**Phase 3** (10+ images): +- Edge cases: very sparse (<30), very dense (>150) +- Different oyster life stages if applicable +- Various environmental conditions + +### Validation Strategy + +- Split data into training (70%), validation (15%), test (15%) +- Use cross-validation for small datasets +- Track metrics over time: + - Mean Absolute Error (MAE) + - Mean Percentage Error (MPE) + - R² correlation between predicted and actual counts + - Precision and recall at different thresholds + +## Output Files + +When running evaluation mode, the system generates: + +1. **Visualization Images** (`detected_*.jpeg`) + - Original image with detected oysters outlined in green + - Bounding boxes around each detection + - Count displayed on image + +2. **Evaluation Results** (`evaluation_results.json`) + - Detailed metrics for each image + - Summary statistics + - Individual image errors and predictions + +3. **Optimization Results** (`optimization_results.json`) + - All parameter combinations tested + - Performance metrics for each + - Best parameters identified + +4. **Best Parameters** (`best_params.json`) + - Optimal parameter values + - Ready to use with `--params` flag + +## Troubleshooting + +### Common Issues + +**Issue**: Severe undercounting +- **Solution**: Decrease `min_area` or increase `max_area` +- **Solution**: Decrease `circularity_threshold` +- **Solution**: Increase `adaptive_block_size` + +**Issue**: Overcounting (many false positives) +- **Solution**: Increase `min_area` or decrease `max_area` +- **Solution**: Increase `circularity_threshold` +- **Solution**: Increase `morph_kernel_size` + +**Issue**: Poor performance in bright/dark areas +- **Solution**: Adjust `adaptive_c` parameter +- **Solution**: Try different preprocessing techniques +- **Solution**: Apply histogram equalization + +**Issue**: Touching oysters counted as one +- **Solution**: Implement watershed segmentation +- **Solution**: Decrease `morph_kernel_size` to reduce merging +- **Solution**: Use distance transform method + +## Technical Notes + +### Computational Performance + +- Processing time: ~0.5-1 second per image (at 0.25x scale) +- Memory usage: ~200-500 MB depending on image size +- Scalable to batch processing + +### Limitations + +1. **Current Approach** + - Works best with well-separated oysters + - Struggles with heavily overlapping specimens + - Sensitive to lighting variations + - Limited by single-image training + +2. **Image Quality Requirements** + - Minimum resolution: 1000x1000 pixels + - Clear focus (not blurry) + - Reasonable contrast between oysters and background + +3. **Biological Variations** + - Assumes similar oyster sizes + - May miscount shells or debris + - Different life stages may need different parameters + +## Contributing + +To improve this system: + +1. Add more labeled training images +2. Experiment with different algorithms +3. Optimize parameters for your specific use case +4. Report issues and results back to the team + +## References + +### Computer Vision Techniques +- Adaptive Thresholding: Automatically adjusts threshold based on local regions +- Morphological Operations: Clean up binary images (opening, closing, erosion, dilation) +- Contour Detection: Find boundaries of objects in binary images +- Watershed Segmentation: Separate touching objects + +### Potential Deep Learning Approaches +- YOLO: "You Only Look Once" - Real-time object detection +- Faster R-CNN: Region-based Convolutional Neural Networks +- U-Net: Semantic segmentation architecture +- Transfer Learning: Use pre-trained models (ImageNet, COCO) + +--- + +## Changelog + +### 2025-10-20 16:36 UTC - Initial Development + +**Created by**: GitHub Copilot +**Status**: Initial implementation complete + +#### Changes Made + +1. **Created Directory Structure** + - Set up `code/count-automation/` directory + - Created README.md with comprehensive documentation + +2. **Implemented Core Algorithm** (`oyster_counter.py`) + - Developed OysterCounter class with configurable parameters + - Implemented preprocessing pipeline: + - Image resizing for performance + - Grayscale conversion + - Gaussian blur for noise reduction + - Implemented detection pipeline: + - Adaptive thresholding for varying lighting + - Morphological operations (opening/closing) + - Contour detection and filtering + - Added shape filtering (area, circularity, aspect ratio) + - Created visualization generation + - Built command-line interface with argparse + - Implemented evaluation mode for labeled datasets + - Added filename parsing for ground truth extraction + +3. **Implemented Parameter Optimization** (`optimize_parameters.py`) + - Built grid search optimization framework + - Defined parameter search space + - Created quick and full optimization modes + - Implemented results tracking and JSON export + +4. **Created Requirements File** (`requirements.txt`) + - Listed all Python dependencies + - Specified minimum version requirements + +5. **Conducted Initial Testing** + - Ran baseline evaluation: MAE=67.0, MPE=63.4% + - Optimized parameters across 96 combinations + - Achieved improved results: MAE=40.6, MPE=38.5% + - Generated visualization images for all 8 test images + +6. **Documentation** + - Created comprehensive README with: + - Installation instructions + - Usage examples + - Algorithm description + - Parameter documentation + - Performance metrics + - Improvement recommendations + - Data collection strategy + - Troubleshooting guide + +#### Performance Summary + +**Dataset**: 8 images (juvenile-99 to juvenile-116) +- Ground truth counts: 99-116 oysters per image +- Image resolution: 4032x3024 pixels (iPhone 14 Pro) + +**Results**: +- Mean Absolute Error: 40.6 oysters +- Mean Percentage Error: 38.5% +- Detection rate: ~61.5% of actual oysters +- Consistent undercounting across all images + +**Best Parameters**: +```json +{ + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 75, + "adaptive_c": 8, + "morph_kernel_size": 7, + "min_area": 300, + "max_area": 40000, + "circularity_threshold": 0.25, + "aspect_ratio_max": 3.0 +} +``` + +#### Next Steps + +1. Collect 20-30 additional labeled images with varying conditions +2. Implement advanced segmentation (watershed, distance transform) +3. Explore deep learning approaches with expanded dataset +4. Develop active learning pipeline for efficient labeling +5. Create ensemble methods for improved robustness + +--- + +### Future Entries + +Future updates to this codebase should be documented here with: +- Date and time (UTC) +- Author/contributor +- Description of changes +- Performance impact (if applicable) +- Any breaking changes or new dependencies diff --git a/code/count-automation/batch_process.py b/code/count-automation/batch_process.py new file mode 100644 index 0000000..ec8bd75 --- /dev/null +++ b/code/count-automation/batch_process.py @@ -0,0 +1,169 @@ +#!/usr/bin/env python3 +""" +Batch Processing Utility for Oyster Counter + +Process multiple images and generate a summary report. + +Author: GitHub Copilot +Created: 2025-10-20 +""" + +import argparse +import json +from pathlib import Path +import pandas as pd +from oyster_counter import OysterCounter +import cv2 +import sys + + +def batch_process(input_dir: str, output_dir: str, params_file: str = None, + generate_vis: bool = True) -> pd.DataFrame: + """ + Process all images in a directory and generate summary report. + + Args: + input_dir: Directory containing images to process + output_dir: Directory to save results + params_file: Optional JSON file with custom parameters + generate_vis: Whether to generate visualization images + + Returns: + DataFrame with results for all images + """ + # Load parameters if provided + params = None + if params_file: + with open(params_file, 'r') as f: + params = json.load(f) + + # Create counter + counter = OysterCounter(params) + + # Create output directory + output_path = Path(output_dir) + output_path.mkdir(parents=True, exist_ok=True) + + # Find all image files + input_path = Path(input_dir) + image_extensions = ['*.jpg', '*.jpeg', '*.png', '*.JPG', '*.JPEG', '*.PNG'] + image_files = [] + for ext in image_extensions: + image_files.extend(input_path.glob(ext)) + + if not image_files: + print(f"No images found in {input_dir}") + return pd.DataFrame() + + print(f"Found {len(image_files)} images to process") + print("="*60) + + results = [] + + for i, img_file in enumerate(sorted(image_files)): + try: + print(f"\nProcessing {i+1}/{len(image_files)}: {img_file.name}") + + # Count oysters + count, vis_image = counter.count_oysters(str(img_file)) + + print(f" Detected: {count} oysters") + + results.append({ + 'filename': img_file.name, + 'count': count, + 'image_path': str(img_file.absolute()) + }) + + # Save visualization if requested + if generate_vis: + vis_file = output_path / f"detected_{img_file.name}" + cv2.imwrite(str(vis_file), vis_image) + print(f" Saved visualization: {vis_file.name}") + + except Exception as e: + print(f" Error processing {img_file.name}: {e}") + results.append({ + 'filename': img_file.name, + 'count': -1, + 'error': str(e), + 'image_path': str(img_file.absolute()) + }) + + # Create DataFrame + df = pd.DataFrame(results) + + # Save to CSV + csv_file = output_path / 'batch_results.csv' + df.to_csv(csv_file, index=False) + print(f"\n{'='*60}") + print(f"Results saved to: {csv_file}") + + # Save to JSON + json_file = output_path / 'batch_results.json' + with open(json_file, 'w') as f: + json.dump(results, f, indent=2) + print(f"Results saved to: {json_file}") + + # Print summary statistics + if len(df[df['count'] >= 0]) > 0: + print(f"\n{'='*60}") + print("SUMMARY STATISTICS") + print(f"{'='*60}") + print(f"Total images processed: {len(df)}") + print(f"Successful: {len(df[df['count'] >= 0])}") + print(f"Failed: {len(df[df['count'] < 0])}") + + valid_counts = df[df['count'] >= 0]['count'] + if len(valid_counts) > 0: + print(f"\nOyster Count Statistics:") + print(f" Mean: {valid_counts.mean():.1f}") + print(f" Median: {valid_counts.median():.1f}") + print(f" Min: {valid_counts.min()}") + print(f" Max: {valid_counts.max()}") + print(f" Std Dev: {valid_counts.std():.1f}") + + return df + + +def main(): + """Main function for command-line interface.""" + parser = argparse.ArgumentParser( + description='Batch process images for oyster counting' + ) + parser.add_argument('--input', '-i', required=True, + help='Input directory containing images') + parser.add_argument('--output', '-o', required=True, + help='Output directory for results') + parser.add_argument('--params', '-p', + help='JSON file with custom parameters') + parser.add_argument('--no-vis', action='store_true', + help='Skip generating visualization images') + + args = parser.parse_args() + + print("Oyster Counter - Batch Processing") + print("="*60) + print(f"Input directory: {args.input}") + print(f"Output directory: {args.output}") + if args.params: + print(f"Parameters file: {args.params}") + print() + + # Process images + df = batch_process( + args.input, + args.output, + args.params, + generate_vis=not args.no_vis + ) + + if len(df) == 0: + print("No images were processed successfully.") + sys.exit(1) + + print("\nBatch processing complete!") + + +if __name__ == '__main__': + main() diff --git a/code/count-automation/example_usage.py b/code/count-automation/example_usage.py new file mode 100644 index 0000000..c9b4389 --- /dev/null +++ b/code/count-automation/example_usage.py @@ -0,0 +1,171 @@ +#!/usr/bin/env python3 +""" +Example Usage of Oyster Counter + +This script demonstrates how to use the OysterCounter class programmatically. + +Author: GitHub Copilot +Created: 2025-10-20 +""" + +from oyster_counter import OysterCounter +import json +from pathlib import Path + +# Configuration paths - adjust these based on your setup +DATA_DIR = Path("../../data/images") +OUTPUT_DIR = Path("../../output/count-automation") +BEST_PARAMS_FILE = OUTPUT_DIR / "best_params.json" + + +def example_1_single_image(): + """Example 1: Count oysters in a single image with default parameters.""" + print("="*60) + print("Example 1: Single Image with Default Parameters") + print("="*60) + + # Create counter with default parameters + counter = OysterCounter() + + # Count oysters in an image + image_path = DATA_DIR / "juvenile-102.jpeg" + count, visualization = counter.count_oysters(str(image_path)) + + print(f"Image: {image_path.name}") + print(f"Detected oysters: {count}") + print() + + +def example_2_custom_parameters(): + """Example 2: Count oysters using custom parameters.""" + print("="*60) + print("Example 2: Single Image with Custom Parameters") + print("="*60) + + # Define custom parameters + custom_params = { + 'resize_factor': 0.25, + 'blur_kernel': 5, + 'adaptive_block_size': 75, + 'adaptive_c': 8, + 'morph_kernel_size': 7, + 'min_area': 300, + 'max_area': 40000, + 'circularity_threshold': 0.25, + 'aspect_ratio_max': 3.0, + } + + # Create counter with custom parameters + counter = OysterCounter(params=custom_params) + + # Count oysters + image_path = DATA_DIR / "juvenile-114.jpeg" + count, visualization = counter.count_oysters(str(image_path)) + + print(f"Image: {image_path.name}") + print(f"Detected oysters: {count}") + print() + + +def example_3_load_parameters_from_file(): + """Example 3: Load optimized parameters from JSON file.""" + print("="*60) + print("Example 3: Using Optimized Parameters from File") + print("="*60) + + # Load parameters from file + with open(BEST_PARAMS_FILE, 'r') as f: + params = json.load(f) + + # Create counter with loaded parameters + counter = OysterCounter(params=params) + + # Count oysters in multiple images + images = sorted(DATA_DIR.glob("*.jpeg"))[:3] # First 3 images + + for img in images: + count, _ = counter.count_oysters(str(img)) + print(f"{img.name}: {count} oysters") + + print() + + +def example_4_programmatic_evaluation(): + """Example 4: Evaluate performance programmatically.""" + print("="*60) + print("Example 4: Programmatic Evaluation") + print("="*60) + + from oyster_counter import evaluate_on_dataset + + # Load optimized parameters + with open(BEST_PARAMS_FILE, 'r') as f: + params = json.load(f) + + # Create counter + counter = OysterCounter(params=params) + + # Evaluate on dataset + results = evaluate_on_dataset( + counter, + str(DATA_DIR), + output_dir=None # Don't save visualizations + ) + + print(f"\nEvaluation Results:") + print(f"Number of images: {results['num_images']}") + print(f"Mean Absolute Error: {results['mean_absolute_error']:.2f}") + print(f"Mean Percentage Error: {results['mean_percentage_error']:.1f}%") + print() + + +def example_5_adjust_parameters(): + """Example 5: Experiment with different parameter values.""" + print("="*60) + print("Example 5: Experimenting with Parameters") + print("="*60) + + image_path = DATA_DIR / "juvenile-99.jpeg" + + # Test with different min_area thresholds + min_areas = [200, 300, 400, 500] + + print(f"Testing different min_area values on {image_path.name}:\n") + + for min_area in min_areas: + # Start with default parameters + params = OysterCounter.get_default_params() + # Override specific parameters + params['min_area'] = min_area + params['adaptive_block_size'] = 75 + params['adaptive_c'] = 8 + params['morph_kernel_size'] = 7 + + counter = OysterCounter(params=params) + count, _ = counter.count_oysters(str(image_path)) + + print(f"min_area={min_area:4d}: {count:3d} oysters detected") + + print() + + +def main(): + """Run all examples.""" + print("\n" + "="*60) + print("OYSTER COUNTER - USAGE EXAMPLES") + print("="*60 + "\n") + + # Run examples + example_1_single_image() + example_2_custom_parameters() + example_3_load_parameters_from_file() + example_4_programmatic_evaluation() + example_5_adjust_parameters() + + print("="*60) + print("All examples completed successfully!") + print("="*60) + + +if __name__ == '__main__': + main() diff --git a/code/count-automation/optimize_parameters.py b/code/count-automation/optimize_parameters.py new file mode 100644 index 0000000..7d589cf --- /dev/null +++ b/code/count-automation/optimize_parameters.py @@ -0,0 +1,149 @@ +#!/usr/bin/env python3 +""" +Parameter Optimization for Oyster Counter + +This script performs grid search to find optimal parameters for the oyster counting algorithm. + +Author: GitHub Copilot +Created: 2025-10-20 +""" + +import json +import itertools +from pathlib import Path +import numpy as np +from oyster_counter import OysterCounter, evaluate_on_dataset +import sys + + +def grid_search(image_dir: str, param_grid: dict, output_file: str = None): + """ + Perform grid search over parameter combinations. + + Args: + image_dir: Directory containing labeled images + param_grid: Dictionary of parameter names to lists of values to try + output_file: Optional file to save results + + Returns: + Dictionary with best parameters and results + """ + # Generate all combinations + param_names = list(param_grid.keys()) + param_values = list(param_grid.values()) + combinations = list(itertools.product(*param_values)) + + print(f"Testing {len(combinations)} parameter combinations...") + print("="*80) + + best_mae = float('inf') + best_params = None + best_results = None + all_trials = [] + + for i, combo in enumerate(combinations): + # Create parameter dict + params = OysterCounter.get_default_params() + for name, value in zip(param_names, combo): + params[name] = value + + # Test this combination + counter = OysterCounter(params) + results = evaluate_on_dataset(counter, image_dir, output_dir=None) + + if results['num_images'] > 0: + mae = results['mean_absolute_error'] + mpe = results['mean_percentage_error'] + + trial_info = { + 'trial': i + 1, + 'params': params, + 'mae': mae, + 'mpe': mpe + } + all_trials.append(trial_info) + + print(f"\nTrial {i+1}/{len(combinations)}") + print(f"Parameters: {combo}") + print(f"MAE: {mae:.2f}, MPE: {mpe:.1f}%") + + if mae < best_mae: + best_mae = mae + best_params = params.copy() + best_results = results + print("*** New best result! ***") + + print("\n" + "="*80) + print("OPTIMIZATION COMPLETE") + print("="*80) + print(f"\nBest Mean Absolute Error: {best_mae:.2f}") + print(f"Best Parameters:") + for key, value in best_params.items(): + print(f" {key}: {value}") + + output = { + 'best_params': best_params, + 'best_mae': best_mae, + 'best_mpe': best_results['mean_percentage_error'] if best_results else None, + 'all_trials': all_trials + } + + # Save results if output file specified + if output_file: + with open(output_file, 'w') as f: + json.dump(output, f, indent=2) + print(f"\nOptimization results saved to: {output_file}") + + return output + + +def main(): + """Main function.""" + # Define parameter grid for optimization + # These ranges are based on typical values for blob/object detection + param_grid = { + 'adaptive_block_size': [51, 75, 101, 151], + 'adaptive_c': [5, 10, 15, 20], + 'min_area': [200, 400, 600, 800], + 'max_area': [30000, 50000, 70000], + 'circularity_threshold': [0.2, 0.3, 0.4], + 'morph_kernel_size': [3, 5, 7], + } + + # Use a smaller grid for faster optimization + # You can expand this for more thorough optimization + quick_param_grid = { + 'adaptive_block_size': [75, 101], + 'adaptive_c': [8, 12], + 'min_area': [300, 500, 700], + 'max_area': [40000, 60000], + 'circularity_threshold': [0.25, 0.35], + 'morph_kernel_size': [5, 7], + } + + image_dir = '../../data/images' + output_file = '../../output/count-automation/optimization_results.json' + + # Create output directory + Path(output_file).parent.mkdir(parents=True, exist_ok=True) + + print("Starting parameter optimization...") + print(f"Using {'quick' if len(sys.argv) == 1 else 'full'} parameter grid") + print() + + # Run optimization with quick grid by default + # Use full grid if --full argument is provided + use_full = len(sys.argv) > 1 and sys.argv[1] == '--full' + grid = param_grid if use_full else quick_param_grid + + results = grid_search(image_dir, grid, output_file) + + # Save best parameters to a separate file for easy use + best_params_file = '../../output/count-automation/best_params.json' + with open(best_params_file, 'w') as f: + json.dump(results['best_params'], f, indent=2) + print(f"Best parameters saved to: {best_params_file}") + + +if __name__ == '__main__': + main() diff --git a/code/count-automation/oyster_counter.py b/code/count-automation/oyster_counter.py new file mode 100644 index 0000000..37ad29d --- /dev/null +++ b/code/count-automation/oyster_counter.py @@ -0,0 +1,382 @@ +#!/usr/bin/env python3 +""" +Oyster Counter - Machine Learning Pipeline for Counting Oysters in Images + +This script uses computer vision techniques to detect and count oysters in photographs. +It employs adaptive thresholding, morphological operations, and contour detection. + +Author: GitHub Copilot +Created: 2025-10-20 +""" + +import cv2 +import numpy as np +from skimage import morphology, measure +from pathlib import Path +import json +import argparse +from typing import Tuple, Dict, List +import sys + + +class OysterCounter: + """ + A class for detecting and counting oysters in images using computer vision. + + Attributes: + params (dict): Dictionary of tunable parameters for the detection algorithm + """ + + def __init__(self, params: Dict = None): + """ + Initialize the OysterCounter with detection parameters. + + Args: + params: Dictionary of parameters. If None, uses default values. + """ + self.params = params or self._default_params() + + @staticmethod + def _default_params() -> Dict: + """Return default detection parameters (internal use).""" + return { + 'resize_factor': 0.25, # Resize images for faster processing + 'blur_kernel': 5, # Gaussian blur kernel size + 'adaptive_block_size': 101, # Block size for adaptive threshold + 'adaptive_c': 10, # Constant subtracted from mean in adaptive threshold + 'morph_kernel_size': 5, # Morphological operation kernel size + 'min_area': 500, # Minimum contour area (in resized image) + 'max_area': 50000, # Maximum contour area (in resized image) + 'circularity_threshold': 0.3, # Minimum circularity (0-1) + 'aspect_ratio_max': 3.0, # Maximum aspect ratio + } + + @staticmethod + def get_default_params() -> Dict: + """ + Get a copy of the default detection parameters. + + Returns: + Dictionary containing default parameter values + """ + return OysterCounter._default_params().copy() + + def preprocess_image(self, image: np.ndarray) -> np.ndarray: + """ + Preprocess the image for oyster detection. + + Args: + image: Input BGR image from OpenCV + + Returns: + Preprocessed grayscale image + """ + # Resize for faster processing + scale = self.params['resize_factor'] + resized = cv2.resize(image, None, fx=scale, fy=scale, + interpolation=cv2.INTER_AREA) + + # Convert to grayscale + gray = cv2.cvtColor(resized, cv2.COLOR_BGR2GRAY) + + # Apply Gaussian blur to reduce noise + blurred = cv2.GaussianBlur(gray, + (self.params['blur_kernel'], + self.params['blur_kernel']), 0) + + return blurred + + def detect_oysters(self, preprocessed: np.ndarray) -> Tuple[List, np.ndarray]: + """ + Detect oysters in preprocessed image using contour detection. + + Args: + preprocessed: Preprocessed grayscale image + + Returns: + Tuple of (list of contours, binary mask) + """ + # Adaptive thresholding to handle varying lighting + binary = cv2.adaptiveThreshold( + preprocessed, + 255, + cv2.ADAPTIVE_THRESH_GAUSSIAN_C, + cv2.THRESH_BINARY_INV, + self.params['adaptive_block_size'], + self.params['adaptive_c'] + ) + + # Morphological operations to clean up the binary image + kernel = cv2.getStructuringElement( + cv2.MORPH_ELLIPSE, + (self.params['morph_kernel_size'], self.params['morph_kernel_size']) + ) + + # Opening to remove small noise + opened = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel, iterations=2) + + # Closing to fill small holes + closed = cv2.morphologyEx(opened, cv2.MORPH_CLOSE, kernel, iterations=2) + + # Find contours + contours, _ = cv2.findContours(closed, cv2.RETR_EXTERNAL, + cv2.CHAIN_APPROX_SIMPLE) + + # Filter contours based on size and shape + valid_contours = [] + for contour in contours: + area = cv2.contourArea(contour) + + # Filter by area + if area < self.params['min_area'] or area > self.params['max_area']: + continue + + # Calculate shape features + perimeter = cv2.arcLength(contour, True) + if perimeter == 0: + continue + + # Circularity: 4π*area/perimeter² + circularity = 4 * np.pi * area / (perimeter * perimeter) + + if circularity < self.params['circularity_threshold']: + continue + + # Check aspect ratio + x, y, w, h = cv2.boundingRect(contour) + aspect_ratio = max(w, h) / min(w, h) if min(w, h) > 0 else 0 + + if aspect_ratio > self.params['aspect_ratio_max']: + continue + + valid_contours.append(contour) + + return valid_contours, closed + + def count_oysters(self, image_path: str) -> Tuple[int, np.ndarray]: + """ + Count oysters in an image. + + Args: + image_path: Path to the image file + + Returns: + Tuple of (count, visualization image) + """ + # Read image + image = cv2.imread(image_path) + if image is None: + raise ValueError(f"Could not read image: {image_path}") + + # Preprocess + preprocessed = self.preprocess_image(image) + + # Detect oysters + contours, binary_mask = self.detect_oysters(preprocessed) + + # Create visualization + vis = self.visualize_results(image, contours) + + return len(contours), vis + + def visualize_results(self, original_image: np.ndarray, + contours: List, show_contours: bool = True) -> np.ndarray: + """ + Create a visualization of detection results. + + Args: + original_image: Original BGR image + contours: List of detected contours (in resized coordinates) + show_contours: Whether to draw contours on the image + + Returns: + Visualization image + """ + # Create a copy for visualization + scale = self.params['resize_factor'] + vis = cv2.resize(original_image, None, fx=scale, fy=scale, + interpolation=cv2.INTER_AREA) + + if show_contours and len(contours) > 0: + # Draw contours in green + cv2.drawContours(vis, contours, -1, (0, 255, 0), 2) + + # Draw bounding boxes and labels + for i, contour in enumerate(contours): + x, y, w, h = cv2.boundingRect(contour) + cv2.rectangle(vis, (x, y), (x + w, y + h), (0, 255, 0), 2) + cv2.putText(vis, str(i + 1), (x, y - 10), + cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2) + + # Add count text + count_text = f"Count: {len(contours)}" + cv2.putText(vis, count_text, (10, 30), + cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2) + + return vis + + +def parse_filename(filename: str) -> int: + """ + Extract the oyster count from the filename. + + Args: + filename: Name of the image file (e.g., 'juvenile-102.jpeg') + + Returns: + The actual oyster count + """ + # Remove extension and split by dash + name = Path(filename).stem + parts = name.split('-') + + # Get the last part which should be the count + if len(parts) >= 2: + return int(parts[-1]) + + raise ValueError(f"Could not parse count from filename: {filename}") + + +def evaluate_on_dataset(counter: OysterCounter, image_dir: str, + output_dir: str = None) -> Dict: + """ + Evaluate the counter on a dataset of labeled images. + + Args: + counter: OysterCounter instance + image_dir: Directory containing labeled images + output_dir: Optional directory to save visualization images + + Returns: + Dictionary with evaluation metrics + """ + image_path = Path(image_dir) + images = sorted(image_path.glob("*.jpeg")) + sorted(image_path.glob("*.jpg")) + + results = [] + + for img_file in images: + try: + # Get ground truth from filename + ground_truth = parse_filename(img_file.name) + + # Count oysters + predicted_count, vis_image = counter.count_oysters(str(img_file)) + + # Calculate error + error = predicted_count - ground_truth + abs_error = abs(error) + pct_error = (abs_error / ground_truth * 100) if ground_truth > 0 else 0 + + results.append({ + 'filename': img_file.name, + 'ground_truth': ground_truth, + 'predicted': predicted_count, + 'error': error, + 'abs_error': abs_error, + 'pct_error': pct_error + }) + + print(f"{img_file.name}: GT={ground_truth}, Pred={predicted_count}, " + f"Error={error}, %Error={pct_error:.1f}%") + + # Save visualization if output directory specified + if output_dir: + output_path = Path(output_dir) + output_path.mkdir(parents=True, exist_ok=True) + output_file = output_path / f"detected_{img_file.name}" + cv2.imwrite(str(output_file), vis_image) + + except Exception as e: + print(f"Error processing {img_file.name}: {e}") + + # Calculate summary statistics + if results: + total_abs_error = sum(r['abs_error'] for r in results) + mean_abs_error = total_abs_error / len(results) + mean_pct_error = sum(r['pct_error'] for r in results) / len(results) + + summary = { + 'num_images': len(results), + 'mean_absolute_error': mean_abs_error, + 'mean_percentage_error': mean_pct_error, + 'results': results + } + else: + summary = {'num_images': 0, 'results': []} + + return summary + + +def main(): + """Main function for command-line interface.""" + parser = argparse.ArgumentParser( + description='Count oysters in images using computer vision' + ) + parser.add_argument('--input', '-i', required=True, + help='Input image file or directory') + parser.add_argument('--output', '-o', + help='Output directory for visualizations') + parser.add_argument('--params', '-p', + help='JSON file with custom parameters') + parser.add_argument('--evaluate', '-e', action='store_true', + help='Evaluate on labeled dataset') + + args = parser.parse_args() + + # Load parameters if provided + params = None + if args.params: + with open(args.params, 'r') as f: + params = json.load(f) + + # Create counter + counter = OysterCounter(params) + + input_path = Path(args.input) + + if args.evaluate: + # Evaluate on dataset + if not input_path.is_dir(): + print("Error: --evaluate requires a directory as input") + sys.exit(1) + + print(f"Evaluating on images in: {input_path}") + results = evaluate_on_dataset(counter, str(input_path), args.output) + + print("\n" + "="*60) + print("EVALUATION SUMMARY") + print("="*60) + print(f"Number of images: {results['num_images']}") + if results['num_images'] > 0: + print(f"Mean Absolute Error: {results['mean_absolute_error']:.2f}") + print(f"Mean Percentage Error: {results['mean_percentage_error']:.1f}%") + + # Save results to JSON + if args.output: + output_path = Path(args.output) + output_path.mkdir(parents=True, exist_ok=True) + results_file = output_path / 'evaluation_results.json' + with open(results_file, 'w') as f: + json.dump(results, f, indent=2) + print(f"\nResults saved to: {results_file}") + + elif input_path.is_file(): + # Process single image + count, vis = counter.count_oysters(str(input_path)) + print(f"Detected {count} oysters in {input_path.name}") + + if args.output: + output_path = Path(args.output) + output_path.mkdir(parents=True, exist_ok=True) + output_file = output_path / f"detected_{input_path.name}" + cv2.imwrite(str(output_file), vis) + print(f"Visualization saved to: {output_file}") + + else: + print("Error: Input must be a file or directory") + sys.exit(1) + + +if __name__ == '__main__': + main() diff --git a/code/count-automation/requirements.txt b/code/count-automation/requirements.txt new file mode 100644 index 0000000..1deb06c --- /dev/null +++ b/code/count-automation/requirements.txt @@ -0,0 +1,6 @@ +opencv-python-headless>=4.8.1.78 +numpy>=1.24.0 +scikit-image>=0.21.0 +matplotlib>=3.7.0 +pandas>=2.0.0 +pillow>=10.3.0 diff --git a/output/count-automation/batch/batch_results.csv b/output/count-automation/batch/batch_results.csv new file mode 100644 index 0000000..f705280 --- /dev/null +++ b/output/count-automation/batch/batch_results.csv @@ -0,0 +1,9 @@ +filename,count,image_path +juvenile-102.jpeg,72,/home/runner/work/project-gigas-conditioning/project-gigas-conditioning/code/count-automation/../../data/images/juvenile-102.jpeg +juvenile-103.jpeg,53,/home/runner/work/project-gigas-conditioning/project-gigas-conditioning/code/count-automation/../../data/images/juvenile-103.jpeg +juvenile-106.jpeg,60,/home/runner/work/project-gigas-conditioning/project-gigas-conditioning/code/count-automation/../../data/images/juvenile-106.jpeg +juvenile-114.jpeg,76,/home/runner/work/project-gigas-conditioning/project-gigas-conditioning/code/count-automation/../../data/images/juvenile-114.jpeg +juvenile-116.jpeg,71,/home/runner/work/project-gigas-conditioning/project-gigas-conditioning/code/count-automation/../../data/images/juvenile-116.jpeg +juvenile-99.jpeg,66,/home/runner/work/project-gigas-conditioning/project-gigas-conditioning/code/count-automation/../../data/images/juvenile-99.jpeg +juvenile19-106.jpeg,67,/home/runner/work/project-gigas-conditioning/project-gigas-conditioning/code/count-automation/../../data/images/juvenile19-106.jpeg +juvenile20-99.jpeg,55,/home/runner/work/project-gigas-conditioning/project-gigas-conditioning/code/count-automation/../../data/images/juvenile20-99.jpeg diff --git a/output/count-automation/batch/batch_results.json b/output/count-automation/batch/batch_results.json new file mode 100644 index 0000000..76f56dc --- /dev/null +++ b/output/count-automation/batch/batch_results.json @@ -0,0 +1,42 @@ +[ + { + "filename": "juvenile-102.jpeg", + "count": 72, + "image_path": "/home/runner/work/project-gigas-conditioning/project-gigas-conditioning/code/count-automation/../../data/images/juvenile-102.jpeg" + }, + { + "filename": "juvenile-103.jpeg", + "count": 53, + "image_path": "/home/runner/work/project-gigas-conditioning/project-gigas-conditioning/code/count-automation/../../data/images/juvenile-103.jpeg" + }, + { + "filename": "juvenile-106.jpeg", + "count": 60, + "image_path": "/home/runner/work/project-gigas-conditioning/project-gigas-conditioning/code/count-automation/../../data/images/juvenile-106.jpeg" + }, + { + "filename": "juvenile-114.jpeg", + "count": 76, + "image_path": "/home/runner/work/project-gigas-conditioning/project-gigas-conditioning/code/count-automation/../../data/images/juvenile-114.jpeg" + }, + { + "filename": "juvenile-116.jpeg", + "count": 71, + "image_path": "/home/runner/work/project-gigas-conditioning/project-gigas-conditioning/code/count-automation/../../data/images/juvenile-116.jpeg" + }, + { + "filename": "juvenile-99.jpeg", + "count": 66, + "image_path": "/home/runner/work/project-gigas-conditioning/project-gigas-conditioning/code/count-automation/../../data/images/juvenile-99.jpeg" + }, + { + "filename": "juvenile19-106.jpeg", + "count": 67, + "image_path": "/home/runner/work/project-gigas-conditioning/project-gigas-conditioning/code/count-automation/../../data/images/juvenile19-106.jpeg" + }, + { + "filename": "juvenile20-99.jpeg", + "count": 55, + "image_path": "/home/runner/work/project-gigas-conditioning/project-gigas-conditioning/code/count-automation/../../data/images/juvenile20-99.jpeg" + } +] \ No newline at end of file diff --git a/output/count-automation/best_params.json b/output/count-automation/best_params.json new file mode 100644 index 0000000..66d5fd3 --- /dev/null +++ b/output/count-automation/best_params.json @@ -0,0 +1,11 @@ +{ + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 75, + "adaptive_c": 8, + "morph_kernel_size": 7, + "min_area": 300, + "max_area": 40000, + "circularity_threshold": 0.25, + "aspect_ratio_max": 3.0 +} \ No newline at end of file diff --git a/output/count-automation/detected_juvenile-102.jpeg b/output/count-automation/detected_juvenile-102.jpeg new file mode 100644 index 0000000..a8a5217 Binary files /dev/null and b/output/count-automation/detected_juvenile-102.jpeg differ diff --git a/output/count-automation/detected_juvenile-103.jpeg b/output/count-automation/detected_juvenile-103.jpeg new file mode 100644 index 0000000..bebd3a7 Binary files /dev/null and b/output/count-automation/detected_juvenile-103.jpeg differ diff --git a/output/count-automation/detected_juvenile-106.jpeg b/output/count-automation/detected_juvenile-106.jpeg new file mode 100644 index 0000000..519118e Binary files /dev/null and b/output/count-automation/detected_juvenile-106.jpeg differ diff --git a/output/count-automation/detected_juvenile-114.jpeg b/output/count-automation/detected_juvenile-114.jpeg new file mode 100644 index 0000000..71f1e3e Binary files /dev/null and b/output/count-automation/detected_juvenile-114.jpeg differ diff --git a/output/count-automation/detected_juvenile-116.jpeg b/output/count-automation/detected_juvenile-116.jpeg new file mode 100644 index 0000000..635c3a3 Binary files /dev/null and b/output/count-automation/detected_juvenile-116.jpeg differ diff --git a/output/count-automation/detected_juvenile-99.jpeg b/output/count-automation/detected_juvenile-99.jpeg new file mode 100644 index 0000000..a972b17 Binary files /dev/null and b/output/count-automation/detected_juvenile-99.jpeg differ diff --git a/output/count-automation/detected_juvenile19-106.jpeg b/output/count-automation/detected_juvenile19-106.jpeg new file mode 100644 index 0000000..ff98061 Binary files /dev/null and b/output/count-automation/detected_juvenile19-106.jpeg differ diff --git a/output/count-automation/detected_juvenile20-99.jpeg b/output/count-automation/detected_juvenile20-99.jpeg new file mode 100644 index 0000000..71cfd2a Binary files /dev/null and b/output/count-automation/detected_juvenile20-99.jpeg differ diff --git a/output/count-automation/evaluation_results.json b/output/count-automation/evaluation_results.json new file mode 100644 index 0000000..b99ef58 --- /dev/null +++ b/output/count-automation/evaluation_results.json @@ -0,0 +1,71 @@ +{ + "num_images": 8, + "mean_absolute_error": 40.625, + "mean_percentage_error": 38.50604347886759, + "results": [ + { + "filename": "juvenile-102.jpeg", + "ground_truth": 102, + "predicted": 72, + "error": -30, + "abs_error": 30, + "pct_error": 29.411764705882355 + }, + { + "filename": "juvenile-103.jpeg", + "ground_truth": 103, + "predicted": 53, + "error": -50, + "abs_error": 50, + "pct_error": 48.54368932038835 + }, + { + "filename": "juvenile-106.jpeg", + "ground_truth": 106, + "predicted": 60, + "error": -46, + "abs_error": 46, + "pct_error": 43.39622641509434 + }, + { + "filename": "juvenile-114.jpeg", + "ground_truth": 114, + "predicted": 76, + "error": -38, + "abs_error": 38, + "pct_error": 33.33333333333333 + }, + { + "filename": "juvenile-116.jpeg", + "ground_truth": 116, + "predicted": 71, + "error": -45, + "abs_error": 45, + "pct_error": 38.793103448275865 + }, + { + "filename": "juvenile-99.jpeg", + "ground_truth": 99, + "predicted": 66, + "error": -33, + "abs_error": 33, + "pct_error": 33.33333333333333 + }, + { + "filename": "juvenile19-106.jpeg", + "ground_truth": 106, + "predicted": 67, + "error": -39, + "abs_error": 39, + "pct_error": 36.79245283018868 + }, + { + "filename": "juvenile20-99.jpeg", + "ground_truth": 99, + "predicted": 55, + "error": -44, + "abs_error": 44, + "pct_error": 44.44444444444444 + } + ] +} \ No newline at end of file diff --git a/output/count-automation/optimization_results.json b/output/count-automation/optimization_results.json new file mode 100644 index 0000000..581f4cf --- /dev/null +++ b/output/count-automation/optimization_results.json @@ -0,0 +1,1553 @@ +{ + "best_params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 75, + "adaptive_c": 8, + "morph_kernel_size": 7, + "min_area": 300, + "max_area": 40000, + "circularity_threshold": 0.25, + "aspect_ratio_max": 3.0 + }, + "best_mae": 40.625, + "best_mpe": 38.50604347886759, + "all_trials": [ + { + "trial": 1, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 75, + "adaptive_c": 8, + "morph_kernel_size": 5, + "min_area": 300, + "max_area": 40000, + "circularity_threshold": 0.25, + "aspect_ratio_max": 3.0 + }, + "mae": 49.375, + "mpe": 46.79491986718077 + }, + { + "trial": 2, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 75, + "adaptive_c": 8, + "morph_kernel_size": 7, + "min_area": 300, + "max_area": 40000, + "circularity_threshold": 0.25, + "aspect_ratio_max": 3.0 + }, + "mae": 40.625, + "mpe": 38.50604347886759 + }, + { + "trial": 3, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 75, + "adaptive_c": 8, + "morph_kernel_size": 5, + "min_area": 300, + "max_area": 40000, + "circularity_threshold": 0.35, + "aspect_ratio_max": 3.0 + }, + "mae": 61.5, + "mpe": 58.25079100049286 + }, + { + "trial": 4, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 75, + "adaptive_c": 8, + "morph_kernel_size": 7, + "min_area": 300, + "max_area": 40000, + "circularity_threshold": 0.35, + "aspect_ratio_max": 3.0 + }, + "mae": 47.375, + "mpe": 44.90349146582642 + }, + { + "trial": 5, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 75, + "adaptive_c": 8, + "morph_kernel_size": 5, + "min_area": 300, + "max_area": 60000, + "circularity_threshold": 0.25, + "aspect_ratio_max": 3.0 + }, + "mae": 49.375, + "mpe": 46.79491986718077 + }, + { + "trial": 6, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 75, + "adaptive_c": 8, + "morph_kernel_size": 7, + "min_area": 300, + "max_area": 60000, + "circularity_threshold": 0.25, + "aspect_ratio_max": 3.0 + }, + "mae": 40.625, + "mpe": 38.50604347886759 + }, + { + "trial": 7, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 75, + "adaptive_c": 8, + "morph_kernel_size": 5, + "min_area": 300, + "max_area": 60000, + "circularity_threshold": 0.35, + "aspect_ratio_max": 3.0 + }, + "mae": 61.5, + "mpe": 58.25079100049286 + }, + { + "trial": 8, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 75, + "adaptive_c": 8, + "morph_kernel_size": 7, + "min_area": 300, + "max_area": 60000, + "circularity_threshold": 0.35, + "aspect_ratio_max": 3.0 + }, + "mae": 47.375, + "mpe": 44.90349146582642 + }, + { + "trial": 9, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 75, + "adaptive_c": 8, + "morph_kernel_size": 5, + "min_area": 500, + "max_area": 40000, + "circularity_threshold": 0.25, + "aspect_ratio_max": 3.0 + }, + "mae": 60.875, + "mpe": 57.734685766966294 + }, + { + "trial": 10, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 75, + "adaptive_c": 8, + "morph_kernel_size": 7, + "min_area": 500, + "max_area": 40000, + "circularity_threshold": 0.25, + "aspect_ratio_max": 3.0 + }, + "mae": 61.0, + "mpe": 57.83423454757381 + }, + { + "trial": 11, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 75, + "adaptive_c": 8, + "morph_kernel_size": 5, + "min_area": 500, + "max_area": 40000, + "circularity_threshold": 0.35, + "aspect_ratio_max": 3.0 + }, + "mae": 71.5, + "mpe": 67.79196142464284 + }, + { + "trial": 12, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 75, + "adaptive_c": 8, + "morph_kernel_size": 7, + "min_area": 500, + "max_area": 40000, + "circularity_threshold": 0.35, + "aspect_ratio_max": 3.0 + }, + "mae": 67.375, + "mpe": 63.8778462571611 + }, + { + "trial": 13, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 75, + "adaptive_c": 8, + "morph_kernel_size": 5, + "min_area": 500, + "max_area": 60000, + "circularity_threshold": 0.25, + "aspect_ratio_max": 3.0 + }, + "mae": 60.875, + "mpe": 57.734685766966294 + }, + { + "trial": 14, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 75, + "adaptive_c": 8, + "morph_kernel_size": 7, + "min_area": 500, + "max_area": 60000, + "circularity_threshold": 0.25, + "aspect_ratio_max": 3.0 + }, + "mae": 61.0, + "mpe": 57.83423454757381 + }, + { + "trial": 15, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 75, + "adaptive_c": 8, + "morph_kernel_size": 5, + "min_area": 500, + "max_area": 60000, + "circularity_threshold": 0.35, + "aspect_ratio_max": 3.0 + }, + "mae": 71.5, + "mpe": 67.79196142464284 + }, + { + "trial": 16, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 75, + "adaptive_c": 8, + "morph_kernel_size": 7, + "min_area": 500, + "max_area": 60000, + "circularity_threshold": 0.35, + "aspect_ratio_max": 3.0 + }, + "mae": 67.375, + "mpe": 63.8778462571611 + }, + { + "trial": 17, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 75, + "adaptive_c": 8, + "morph_kernel_size": 5, + "min_area": 700, + "max_area": 40000, + "circularity_threshold": 0.25, + "aspect_ratio_max": 3.0 + }, + "mae": 70.125, + "mpe": 66.57599492576162 + }, + { + "trial": 18, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 75, + "adaptive_c": 8, + "morph_kernel_size": 7, + "min_area": 700, + "max_area": 40000, + "circularity_threshold": 0.25, + "aspect_ratio_max": 3.0 + }, + "mae": 74.75, + "mpe": 70.86524828998712 + }, + { + "trial": 19, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 75, + "adaptive_c": 8, + "morph_kernel_size": 5, + "min_area": 700, + "max_area": 40000, + "circularity_threshold": 0.35, + "aspect_ratio_max": 3.0 + }, + "mae": 79.375, + "mpe": 75.32146860503724 + }, + { + "trial": 20, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 75, + "adaptive_c": 8, + "morph_kernel_size": 7, + "min_area": 700, + "max_area": 40000, + "circularity_threshold": 0.35, + "aspect_ratio_max": 3.0 + }, + "mae": 80.375, + "mpe": 76.19017805014789 + }, + { + "trial": 21, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 75, + "adaptive_c": 8, + "morph_kernel_size": 5, + "min_area": 700, + "max_area": 60000, + "circularity_threshold": 0.25, + "aspect_ratio_max": 3.0 + }, + "mae": 70.125, + "mpe": 66.57599492576162 + }, + { + "trial": 22, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 75, + "adaptive_c": 8, + "morph_kernel_size": 7, + "min_area": 700, + "max_area": 60000, + "circularity_threshold": 0.25, + "aspect_ratio_max": 3.0 + }, + "mae": 74.75, + "mpe": 70.86524828998712 + }, + { + "trial": 23, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 75, + "adaptive_c": 8, + "morph_kernel_size": 5, + "min_area": 700, + "max_area": 60000, + "circularity_threshold": 0.35, + "aspect_ratio_max": 3.0 + }, + "mae": 79.375, + "mpe": 75.32146860503724 + }, + { + "trial": 24, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 75, + "adaptive_c": 8, + "morph_kernel_size": 7, + "min_area": 700, + "max_area": 60000, + "circularity_threshold": 0.35, + "aspect_ratio_max": 3.0 + }, + "mae": 80.375, + "mpe": 76.19017805014789 + }, + { + "trial": 25, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 75, + "adaptive_c": 12, + "morph_kernel_size": 5, + "min_area": 300, + "max_area": 40000, + "circularity_threshold": 0.25, + "aspect_ratio_max": 3.0 + }, + "mae": 45.75, + "mpe": 43.16912404517442 + }, + { + "trial": 26, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 75, + "adaptive_c": 12, + "morph_kernel_size": 7, + "min_area": 300, + "max_area": 40000, + "circularity_threshold": 0.25, + "aspect_ratio_max": 3.0 + }, + "mae": 48.0, + "mpe": 45.46203502327126 + }, + { + "trial": 27, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 75, + "adaptive_c": 12, + "morph_kernel_size": 5, + "min_area": 300, + "max_area": 40000, + "circularity_threshold": 0.35, + "aspect_ratio_max": 3.0 + }, + "mae": 59.625, + "mpe": 56.30635825574482 + }, + { + "trial": 28, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 75, + "adaptive_c": 12, + "morph_kernel_size": 7, + "min_area": 300, + "max_area": 40000, + "circularity_threshold": 0.35, + "aspect_ratio_max": 3.0 + }, + "mae": 52.25, + "mpe": 49.508058953283985 + }, + { + "trial": 29, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 75, + "adaptive_c": 12, + "morph_kernel_size": 5, + "min_area": 300, + "max_area": 60000, + "circularity_threshold": 0.25, + "aspect_ratio_max": 3.0 + }, + "mae": 45.75, + "mpe": 43.16912404517442 + }, + { + "trial": 30, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 75, + "adaptive_c": 12, + "morph_kernel_size": 7, + "min_area": 300, + "max_area": 60000, + "circularity_threshold": 0.25, + "aspect_ratio_max": 3.0 + }, + "mae": 48.0, + "mpe": 45.46203502327126 + }, + { + "trial": 31, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 75, + "adaptive_c": 12, + "morph_kernel_size": 5, + "min_area": 300, + "max_area": 60000, + "circularity_threshold": 0.35, + "aspect_ratio_max": 3.0 + }, + "mae": 59.625, + "mpe": 56.30635825574482 + }, + { + "trial": 32, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 75, + "adaptive_c": 12, + "morph_kernel_size": 7, + "min_area": 300, + "max_area": 60000, + "circularity_threshold": 0.35, + "aspect_ratio_max": 3.0 + }, + "mae": 52.25, + "mpe": 49.508058953283985 + }, + { + "trial": 33, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 75, + "adaptive_c": 12, + "morph_kernel_size": 5, + "min_area": 500, + "max_area": 40000, + "circularity_threshold": 0.25, + "aspect_ratio_max": 3.0 + }, + "mae": 61.875, + "mpe": 58.61859124281765 + }, + { + "trial": 34, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 75, + "adaptive_c": 12, + "morph_kernel_size": 7, + "min_area": 500, + "max_area": 40000, + "circularity_threshold": 0.25, + "aspect_ratio_max": 3.0 + }, + "mae": 70.875, + "mpe": 67.23237061016751 + }, + { + "trial": 35, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 75, + "adaptive_c": 12, + "morph_kernel_size": 5, + "min_area": 500, + "max_area": 40000, + "circularity_threshold": 0.35, + "aspect_ratio_max": 3.0 + }, + "mae": 73.875, + "mpe": 69.97878370308618 + }, + { + "trial": 36, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 75, + "adaptive_c": 12, + "morph_kernel_size": 7, + "min_area": 500, + "max_area": 40000, + "circularity_threshold": 0.35, + "aspect_ratio_max": 3.0 + }, + "mae": 74.875, + "mpe": 71.04808689988275 + }, + { + "trial": 37, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 75, + "adaptive_c": 12, + "morph_kernel_size": 5, + "min_area": 500, + "max_area": 60000, + "circularity_threshold": 0.25, + "aspect_ratio_max": 3.0 + }, + "mae": 61.875, + "mpe": 58.61859124281765 + }, + { + "trial": 38, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 75, + "adaptive_c": 12, + "morph_kernel_size": 7, + "min_area": 500, + "max_area": 60000, + "circularity_threshold": 0.25, + "aspect_ratio_max": 3.0 + }, + "mae": 70.875, + "mpe": 67.23237061016751 + }, + { + "trial": 39, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 75, + "adaptive_c": 12, + "morph_kernel_size": 5, + "min_area": 500, + "max_area": 60000, + "circularity_threshold": 0.35, + "aspect_ratio_max": 3.0 + }, + "mae": 73.875, + "mpe": 69.97878370308618 + }, + { + "trial": 40, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 75, + "adaptive_c": 12, + "morph_kernel_size": 7, + "min_area": 500, + "max_area": 60000, + "circularity_threshold": 0.35, + "aspect_ratio_max": 3.0 + }, + "mae": 74.875, + "mpe": 71.04808689988275 + }, + { + "trial": 41, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 75, + "adaptive_c": 12, + "morph_kernel_size": 5, + "min_area": 700, + "max_area": 40000, + "circularity_threshold": 0.25, + "aspect_ratio_max": 3.0 + }, + "mae": 74.0, + "mpe": 70.14387754627052 + }, + { + "trial": 42, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 75, + "adaptive_c": 12, + "morph_kernel_size": 7, + "min_area": 700, + "max_area": 40000, + "circularity_threshold": 0.25, + "aspect_ratio_max": 3.0 + }, + "mae": 83.875, + "mpe": 79.49838271693058 + }, + { + "trial": 43, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 75, + "adaptive_c": 12, + "morph_kernel_size": 5, + "min_area": 700, + "max_area": 40000, + "circularity_threshold": 0.35, + "aspect_ratio_max": 3.0 + }, + "mae": 84.0, + "mpe": 79.58058130112491 + }, + { + "trial": 44, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 75, + "adaptive_c": 12, + "morph_kernel_size": 7, + "min_area": 700, + "max_area": 40000, + "circularity_threshold": 0.35, + "aspect_ratio_max": 3.0 + }, + "mae": 87.5, + "mpe": 82.93902473451271 + }, + { + "trial": 45, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 75, + "adaptive_c": 12, + "morph_kernel_size": 5, + "min_area": 700, + "max_area": 60000, + "circularity_threshold": 0.25, + "aspect_ratio_max": 3.0 + }, + "mae": 74.0, + "mpe": 70.14387754627052 + }, + { + "trial": 46, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 75, + "adaptive_c": 12, + "morph_kernel_size": 7, + "min_area": 700, + "max_area": 60000, + "circularity_threshold": 0.25, + "aspect_ratio_max": 3.0 + }, + "mae": 83.875, + "mpe": 79.49838271693058 + }, + { + "trial": 47, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 75, + "adaptive_c": 12, + "morph_kernel_size": 5, + "min_area": 700, + "max_area": 60000, + "circularity_threshold": 0.35, + "aspect_ratio_max": 3.0 + }, + "mae": 84.0, + "mpe": 79.58058130112491 + }, + { + "trial": 48, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 75, + "adaptive_c": 12, + "morph_kernel_size": 7, + "min_area": 700, + "max_area": 60000, + "circularity_threshold": 0.35, + "aspect_ratio_max": 3.0 + }, + "mae": 87.5, + "mpe": 82.93902473451271 + }, + { + "trial": 49, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 101, + "adaptive_c": 8, + "morph_kernel_size": 5, + "min_area": 300, + "max_area": 40000, + "circularity_threshold": 0.25, + "aspect_ratio_max": 3.0 + }, + "mae": 59.0, + "mpe": 55.81695704960529 + }, + { + "trial": 50, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 101, + "adaptive_c": 8, + "morph_kernel_size": 7, + "min_area": 300, + "max_area": 40000, + "circularity_threshold": 0.25, + "aspect_ratio_max": 3.0 + }, + "mae": 47.625, + "mpe": 44.99056949591444 + }, + { + "trial": 51, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 101, + "adaptive_c": 8, + "morph_kernel_size": 5, + "min_area": 300, + "max_area": 40000, + "circularity_threshold": 0.35, + "aspect_ratio_max": 3.0 + }, + "mae": 68.5, + "mpe": 64.84214439417552 + }, + { + "trial": 52, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 101, + "adaptive_c": 8, + "morph_kernel_size": 7, + "min_area": 300, + "max_area": 40000, + "circularity_threshold": 0.35, + "aspect_ratio_max": 3.0 + }, + "mae": 54.25, + "mpe": 51.26751924421088 + }, + { + "trial": 53, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 101, + "adaptive_c": 8, + "morph_kernel_size": 5, + "min_area": 300, + "max_area": 60000, + "circularity_threshold": 0.25, + "aspect_ratio_max": 3.0 + }, + "mae": 59.0, + "mpe": 55.81695704960529 + }, + { + "trial": 54, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 101, + "adaptive_c": 8, + "morph_kernel_size": 7, + "min_area": 300, + "max_area": 60000, + "circularity_threshold": 0.25, + "aspect_ratio_max": 3.0 + }, + "mae": 47.625, + "mpe": 44.99056949591444 + }, + { + "trial": 55, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 101, + "adaptive_c": 8, + "morph_kernel_size": 5, + "min_area": 300, + "max_area": 60000, + "circularity_threshold": 0.35, + "aspect_ratio_max": 3.0 + }, + "mae": 68.5, + "mpe": 64.84214439417552 + }, + { + "trial": 56, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 101, + "adaptive_c": 8, + "morph_kernel_size": 7, + "min_area": 300, + "max_area": 60000, + "circularity_threshold": 0.35, + "aspect_ratio_max": 3.0 + }, + "mae": 54.25, + "mpe": 51.26751924421088 + }, + { + "trial": 57, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 101, + "adaptive_c": 8, + "morph_kernel_size": 5, + "min_area": 500, + "max_area": 40000, + "circularity_threshold": 0.25, + "aspect_ratio_max": 3.0 + }, + "mae": 65.5, + "mpe": 62.0260565979622 + }, + { + "trial": 58, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 101, + "adaptive_c": 8, + "morph_kernel_size": 7, + "min_area": 500, + "max_area": 40000, + "circularity_threshold": 0.25, + "aspect_ratio_max": 3.0 + }, + "mae": 59.875, + "mpe": 56.56508806602034 + }, + { + "trial": 59, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 101, + "adaptive_c": 8, + "morph_kernel_size": 5, + "min_area": 500, + "max_area": 40000, + "circularity_threshold": 0.35, + "aspect_ratio_max": 3.0 + }, + "mae": 74.375, + "mpe": 70.44824162271559 + }, + { + "trial": 60, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 101, + "adaptive_c": 8, + "morph_kernel_size": 7, + "min_area": 500, + "max_area": 40000, + "circularity_threshold": 0.35, + "aspect_ratio_max": 3.0 + }, + "mae": 66.375, + "mpe": 62.734279193627124 + }, + { + "trial": 61, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 101, + "adaptive_c": 8, + "morph_kernel_size": 5, + "min_area": 500, + "max_area": 60000, + "circularity_threshold": 0.25, + "aspect_ratio_max": 3.0 + }, + "mae": 65.5, + "mpe": 62.0260565979622 + }, + { + "trial": 62, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 101, + "adaptive_c": 8, + "morph_kernel_size": 7, + "min_area": 500, + "max_area": 60000, + "circularity_threshold": 0.25, + "aspect_ratio_max": 3.0 + }, + "mae": 59.875, + "mpe": 56.56508806602034 + }, + { + "trial": 63, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 101, + "adaptive_c": 8, + "morph_kernel_size": 5, + "min_area": 500, + "max_area": 60000, + "circularity_threshold": 0.35, + "aspect_ratio_max": 3.0 + }, + "mae": 74.375, + "mpe": 70.44824162271559 + }, + { + "trial": 64, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 101, + "adaptive_c": 8, + "morph_kernel_size": 7, + "min_area": 500, + "max_area": 60000, + "circularity_threshold": 0.35, + "aspect_ratio_max": 3.0 + }, + "mae": 66.375, + "mpe": 62.734279193627124 + }, + { + "trial": 65, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 101, + "adaptive_c": 8, + "morph_kernel_size": 5, + "min_area": 700, + "max_area": 40000, + "circularity_threshold": 0.25, + "aspect_ratio_max": 3.0 + }, + "mae": 70.625, + "mpe": 66.91141639101622 + }, + { + "trial": 66, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 101, + "adaptive_c": 8, + "morph_kernel_size": 7, + "min_area": 700, + "max_area": 40000, + "circularity_threshold": 0.25, + "aspect_ratio_max": 3.0 + }, + "mae": 67.125, + "mpe": 63.59681536363782 + }, + { + "trial": 67, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 101, + "adaptive_c": 8, + "morph_kernel_size": 5, + "min_area": 700, + "max_area": 40000, + "circularity_threshold": 0.35, + "aspect_ratio_max": 3.0 + }, + "mae": 79.125, + "mpe": 74.97142704043732 + }, + { + "trial": 68, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 101, + "adaptive_c": 8, + "morph_kernel_size": 7, + "min_area": 700, + "max_area": 40000, + "circularity_threshold": 0.35, + "aspect_ratio_max": 3.0 + }, + "mae": 73.375, + "mpe": 69.51348123871935 + }, + { + "trial": 69, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 101, + "adaptive_c": 8, + "morph_kernel_size": 5, + "min_area": 700, + "max_area": 60000, + "circularity_threshold": 0.25, + "aspect_ratio_max": 3.0 + }, + "mae": 70.625, + "mpe": 66.91141639101622 + }, + { + "trial": 70, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 101, + "adaptive_c": 8, + "morph_kernel_size": 7, + "min_area": 700, + "max_area": 60000, + "circularity_threshold": 0.25, + "aspect_ratio_max": 3.0 + }, + "mae": 67.125, + "mpe": 63.59681536363782 + }, + { + "trial": 71, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 101, + "adaptive_c": 8, + "morph_kernel_size": 5, + "min_area": 700, + "max_area": 60000, + "circularity_threshold": 0.35, + "aspect_ratio_max": 3.0 + }, + "mae": 79.125, + "mpe": 74.97142704043732 + }, + { + "trial": 72, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 101, + "adaptive_c": 8, + "morph_kernel_size": 7, + "min_area": 700, + "max_area": 60000, + "circularity_threshold": 0.35, + "aspect_ratio_max": 3.0 + }, + "mae": 73.375, + "mpe": 69.51348123871935 + }, + { + "trial": 73, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 101, + "adaptive_c": 12, + "morph_kernel_size": 5, + "min_area": 300, + "max_area": 40000, + "circularity_threshold": 0.25, + "aspect_ratio_max": 3.0 + }, + "mae": 49.5, + "mpe": 46.87308693222469 + }, + { + "trial": 74, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 101, + "adaptive_c": 12, + "morph_kernel_size": 7, + "min_area": 300, + "max_area": 40000, + "circularity_threshold": 0.25, + "aspect_ratio_max": 3.0 + }, + "mae": 43.125, + "mpe": 40.656584636904455 + }, + { + "trial": 75, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 101, + "adaptive_c": 12, + "morph_kernel_size": 5, + "min_area": 300, + "max_area": 40000, + "circularity_threshold": 0.35, + "aspect_ratio_max": 3.0 + }, + "mae": 59.75, + "mpe": 56.51070090182374 + }, + { + "trial": 76, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 101, + "adaptive_c": 12, + "morph_kernel_size": 7, + "min_area": 300, + "max_area": 40000, + "circularity_threshold": 0.35, + "aspect_ratio_max": 3.0 + }, + "mae": 48.875, + "mpe": 46.086426436087834 + }, + { + "trial": 77, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 101, + "adaptive_c": 12, + "morph_kernel_size": 5, + "min_area": 300, + "max_area": 60000, + "circularity_threshold": 0.25, + "aspect_ratio_max": 3.0 + }, + "mae": 49.5, + "mpe": 46.87308693222469 + }, + { + "trial": 78, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 101, + "adaptive_c": 12, + "morph_kernel_size": 7, + "min_area": 300, + "max_area": 60000, + "circularity_threshold": 0.25, + "aspect_ratio_max": 3.0 + }, + "mae": 43.125, + "mpe": 40.656584636904455 + }, + { + "trial": 79, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 101, + "adaptive_c": 12, + "morph_kernel_size": 5, + "min_area": 300, + "max_area": 60000, + "circularity_threshold": 0.35, + "aspect_ratio_max": 3.0 + }, + "mae": 59.75, + "mpe": 56.51070090182374 + }, + { + "trial": 80, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 101, + "adaptive_c": 12, + "morph_kernel_size": 7, + "min_area": 300, + "max_area": 60000, + "circularity_threshold": 0.35, + "aspect_ratio_max": 3.0 + }, + "mae": 48.875, + "mpe": 46.086426436087834 + }, + { + "trial": 81, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 101, + "adaptive_c": 12, + "morph_kernel_size": 5, + "min_area": 500, + "max_area": 40000, + "circularity_threshold": 0.25, + "aspect_ratio_max": 3.0 + }, + "mae": 59.5, + "mpe": 56.30652523667518 + }, + { + "trial": 82, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 101, + "adaptive_c": 12, + "morph_kernel_size": 7, + "min_area": 500, + "max_area": 40000, + "circularity_threshold": 0.25, + "aspect_ratio_max": 3.0 + }, + "mae": 57.25, + "mpe": 54.130831404894366 + }, + { + "trial": 83, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 101, + "adaptive_c": 12, + "morph_kernel_size": 5, + "min_area": 500, + "max_area": 40000, + "circularity_threshold": 0.35, + "aspect_ratio_max": 3.0 + }, + "mae": 68.625, + "mpe": 64.90148068896825 + }, + { + "trial": 84, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 101, + "adaptive_c": 12, + "morph_kernel_size": 7, + "min_area": 500, + "max_area": 40000, + "circularity_threshold": 0.35, + "aspect_ratio_max": 3.0 + }, + "mae": 62.875, + "mpe": 59.44274867577586 + }, + { + "trial": 85, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 101, + "adaptive_c": 12, + "morph_kernel_size": 5, + "min_area": 500, + "max_area": 60000, + "circularity_threshold": 0.25, + "aspect_ratio_max": 3.0 + }, + "mae": 59.5, + "mpe": 56.30652523667518 + }, + { + "trial": 86, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 101, + "adaptive_c": 12, + "morph_kernel_size": 7, + "min_area": 500, + "max_area": 60000, + "circularity_threshold": 0.25, + "aspect_ratio_max": 3.0 + }, + "mae": 57.25, + "mpe": 54.130831404894366 + }, + { + "trial": 87, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 101, + "adaptive_c": 12, + "morph_kernel_size": 5, + "min_area": 500, + "max_area": 60000, + "circularity_threshold": 0.35, + "aspect_ratio_max": 3.0 + }, + "mae": 68.625, + "mpe": 64.90148068896825 + }, + { + "trial": 88, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 101, + "adaptive_c": 12, + "morph_kernel_size": 7, + "min_area": 500, + "max_area": 60000, + "circularity_threshold": 0.35, + "aspect_ratio_max": 3.0 + }, + "mae": 62.875, + "mpe": 59.44274867577586 + }, + { + "trial": 89, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 101, + "adaptive_c": 12, + "morph_kernel_size": 5, + "min_area": 700, + "max_area": 40000, + "circularity_threshold": 0.25, + "aspect_ratio_max": 3.0 + }, + "mae": 66.875, + "mpe": 63.36391250970725 + }, + { + "trial": 90, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 101, + "adaptive_c": 12, + "morph_kernel_size": 7, + "min_area": 700, + "max_area": 40000, + "circularity_threshold": 0.25, + "aspect_ratio_max": 3.0 + }, + "mae": 69.625, + "mpe": 65.90737163929732 + }, + { + "trial": 91, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 101, + "adaptive_c": 12, + "morph_kernel_size": 5, + "min_area": 700, + "max_area": 40000, + "circularity_threshold": 0.35, + "aspect_ratio_max": 3.0 + }, + "mae": 75.625, + "mpe": 71.59584539448275 + }, + { + "trial": 92, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 101, + "adaptive_c": 12, + "morph_kernel_size": 7, + "min_area": 700, + "max_area": 40000, + "circularity_threshold": 0.35, + "aspect_ratio_max": 3.0 + }, + "mae": 74.875, + "mpe": 70.86201793780819 + }, + { + "trial": 93, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 101, + "adaptive_c": 12, + "morph_kernel_size": 5, + "min_area": 700, + "max_area": 60000, + "circularity_threshold": 0.25, + "aspect_ratio_max": 3.0 + }, + "mae": 66.875, + "mpe": 63.36391250970725 + }, + { + "trial": 94, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 101, + "adaptive_c": 12, + "morph_kernel_size": 7, + "min_area": 700, + "max_area": 60000, + "circularity_threshold": 0.25, + "aspect_ratio_max": 3.0 + }, + "mae": 69.625, + "mpe": 65.90737163929732 + }, + { + "trial": 95, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 101, + "adaptive_c": 12, + "morph_kernel_size": 5, + "min_area": 700, + "max_area": 60000, + "circularity_threshold": 0.35, + "aspect_ratio_max": 3.0 + }, + "mae": 75.625, + "mpe": 71.59584539448275 + }, + { + "trial": 96, + "params": { + "resize_factor": 0.25, + "blur_kernel": 5, + "adaptive_block_size": 101, + "adaptive_c": 12, + "morph_kernel_size": 7, + "min_area": 700, + "max_area": 60000, + "circularity_threshold": 0.35, + "aspect_ratio_max": 3.0 + }, + "mae": 74.875, + "mpe": 70.86201793780819 + } + ] +} \ No newline at end of file