You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
339 lines
13 KiB
339 lines
13 KiB
#!/usr/bin/env python3
|
|
"""
|
|
Compare .pt model predictions with ONNX model outputs on the same static test image
|
|
Provides detailed debug output to identify differences in preprocessing and inference
|
|
"""
|
|
|
|
import cv2
|
|
import numpy as np
|
|
from ultralytics import YOLO
|
|
import onnxruntime as ort
|
|
import torch
|
|
import os
|
|
from pathlib import Path
|
|
|
|
# Force CPU-only execution to avoid CUDA compatibility issues
|
|
os.environ['CUDA_VISIBLE_DEVICES'] = ''
|
|
torch.cuda.is_available = lambda: False
|
|
|
|
def letterbox(im, new_shape=(640, 640), color=(114, 114, 114), auto=True, scaleFill=False, scaleup=True, stride=32):
|
|
"""Letterbox preprocessing - exact copy of YOLO preprocessing"""
|
|
shape = im.shape[:2] # current shape [height, width]
|
|
if isinstance(new_shape, int):
|
|
new_shape = (new_shape, new_shape)
|
|
|
|
# Scale ratio (new / old)
|
|
r = min(new_shape[0] / shape[0], new_shape[1] / shape[1])
|
|
if not scaleup: # only scale down, do not scale up (for better val mAP)
|
|
r = min(r, 1.0)
|
|
|
|
# Compute padding
|
|
ratio = r, r # width, height ratios
|
|
new_unpad = int(round(shape[1] * r)), int(round(shape[0] * r))
|
|
dw, dh = new_shape[1] - new_unpad[0], new_shape[0] - new_unpad[1] # wh padding
|
|
if auto: # minimum rectangle
|
|
dw, dh = np.mod(dw, stride), np.mod(dh, stride) # wh padding
|
|
elif scaleFill: # stretch
|
|
dw, dh = 0.0, 0.0
|
|
new_unpad = (new_shape[1], new_shape[0])
|
|
ratio = new_shape[1] / shape[1], new_shape[0] / shape[0] # width, height ratios
|
|
|
|
dw /= 2 # divide padding into 2 sides
|
|
dh /= 2
|
|
|
|
if shape[::-1] != new_unpad: # resize
|
|
im = cv2.resize(im, new_unpad, interpolation=cv2.INTER_LINEAR)
|
|
top, bottom = int(round(dh - 0.1)), int(round(dh + 0.1))
|
|
left, right = int(round(dw - 0.1)), int(round(dw + 0.1))
|
|
im = cv2.copyMakeBorder(im, top, bottom, left, right, cv2.BORDER_CONSTANT, value=color) # add border
|
|
return im, ratio, (dw, dh)
|
|
|
|
def preprocess_image(image_path, target_size=(640, 640)):
|
|
"""Preprocess image for ONNX model - matches Android preprocessing"""
|
|
print(f"📸 Loading image: {image_path}")
|
|
|
|
# Load image
|
|
img = cv2.imread(str(image_path))
|
|
if img is None:
|
|
raise ValueError(f"Could not load image: {image_path}")
|
|
|
|
print(f" Original size: {img.shape}")
|
|
|
|
# Apply letterbox (same as YOLO preprocessing)
|
|
img_processed, ratio, pad = letterbox(img, target_size)
|
|
print(f" Letterboxed size: {img_processed.shape}")
|
|
print(f" Scale ratio: {ratio}")
|
|
print(f" Padding (dw, dh): {pad}")
|
|
|
|
# Convert BGR to RGB
|
|
img_rgb = cv2.cvtColor(img_processed, cv2.COLOR_BGR2RGB)
|
|
|
|
# Normalize to [0, 1] and convert to CHW format
|
|
img_normalized = img_rgb.astype(np.float32) / 255.0
|
|
img_chw = np.transpose(img_normalized, (2, 0, 1))
|
|
img_batch = np.expand_dims(img_chw, axis=0)
|
|
|
|
print(f" Final tensor shape: {img_batch.shape}")
|
|
print(f" Value range: [{img_batch.min():.3f}, {img_batch.max():.3f}]")
|
|
|
|
return img_batch, img, ratio, pad
|
|
|
|
def run_pt_model(model_path, image_path):
|
|
"""Run .pt model prediction with full debug output"""
|
|
print("\n🔥 Running .pt model prediction:")
|
|
print(f" Model: {model_path}")
|
|
|
|
# Load model
|
|
model = YOLO(model_path)
|
|
|
|
# Run prediction with verbose output
|
|
results = model.predict(
|
|
source=str(image_path),
|
|
conf=0.01, # Very low confidence to catch everything
|
|
iou=0.5,
|
|
max_det=1000,
|
|
verbose=True,
|
|
save=False
|
|
)
|
|
|
|
result = results[0]
|
|
print(f" Found {len(result.boxes)} detections")
|
|
|
|
# Extract raw data
|
|
if len(result.boxes) > 0:
|
|
boxes = result.boxes.xyxy.cpu().numpy() # x1, y1, x2, y2
|
|
confidences = result.boxes.conf.cpu().numpy()
|
|
classes = result.boxes.cls.cpu().numpy().astype(int)
|
|
|
|
print(f"\n📊 .pt Model Results Summary:")
|
|
print(f" Total detections: {len(boxes)}")
|
|
|
|
# Group by class
|
|
class_counts = {}
|
|
for cls_id in classes:
|
|
class_counts[cls_id] = class_counts.get(cls_id, 0) + 1
|
|
|
|
print(f" Classes found: {sorted(class_counts.keys())}")
|
|
|
|
# Focus on shiny icon (class 50)
|
|
shiny_detections = [(i, conf) for i, (cls_id, conf) in enumerate(zip(classes, confidences)) if cls_id == 50]
|
|
if shiny_detections:
|
|
print(f"\n✨ SHINY ICON DETECTIONS (Class 50):")
|
|
for i, conf in shiny_detections:
|
|
box = boxes[i]
|
|
print(f" Detection {i}: conf={conf:.6f}, box=[{box[0]:.1f},{box[1]:.1f},{box[2]:.1f},{box[3]:.1f}]")
|
|
else:
|
|
print(f"\n❌ NO SHINY ICON DETECTIONS (Class 50)")
|
|
|
|
# Show all detections with confidence > 0.1
|
|
high_conf_detections = [(i, cls_id, conf) for i, (cls_id, conf) in enumerate(zip(classes, confidences)) if conf > 0.1]
|
|
if high_conf_detections:
|
|
print(f"\n🎯 High confidence detections (>0.1):")
|
|
for i, cls_id, conf in high_conf_detections[:10]: # Show top 10
|
|
box = boxes[i]
|
|
print(f" Class {cls_id}: conf={conf:.6f}, box=[{box[0]:.1f},{box[1]:.1f},{box[2]:.1f},{box[3]:.1f}]")
|
|
|
|
return boxes, confidences, classes
|
|
else:
|
|
print(f"\n❌ NO DETECTIONS FOUND")
|
|
return None, None, None
|
|
|
|
def run_onnx_model(model_path, preprocessed_img):
|
|
"""Run ONNX model inference with full debug output"""
|
|
print(f"\n🔧 Running ONNX model inference:")
|
|
print(f" Model: {model_path}")
|
|
|
|
# Load ONNX model
|
|
session = ort.InferenceSession(str(model_path))
|
|
|
|
# Get model info
|
|
input_name = session.get_inputs()[0].name
|
|
output_names = [output.name for output in session.get_outputs()]
|
|
|
|
print(f" Input name: {input_name}")
|
|
print(f" Output names: {output_names}")
|
|
print(f" Input shape: {preprocessed_img.shape}")
|
|
|
|
# Run inference
|
|
outputs = session.run(output_names, {input_name: preprocessed_img})
|
|
|
|
print(f" Number of outputs: {len(outputs)}")
|
|
for i, output in enumerate(outputs):
|
|
print(f" Output {i} shape: {output.shape}")
|
|
|
|
# Process main output (should be detections)
|
|
detections = outputs[0] # Usually the first output contains detections
|
|
|
|
if len(detections.shape) == 3:
|
|
batch_size, num_detections, num_values = detections.shape
|
|
print(f" Detections tensor: [{batch_size}, {num_detections}, {num_values}]")
|
|
|
|
# Extract detections from batch
|
|
detection_data = detections[0] # Remove batch dimension
|
|
|
|
if num_values == 6: # NMS format: [x, y, w, h, conf, class]
|
|
print(f" Format: NMS output (x, y, w, h, conf, class)")
|
|
|
|
# Count valid detections (non-zero confidence)
|
|
valid_mask = detection_data[:, 4] > 0.000001 # conf > 0
|
|
valid_detections = detection_data[valid_mask]
|
|
|
|
print(f" Valid detections: {len(valid_detections)} / {num_detections}")
|
|
|
|
if len(valid_detections) > 0:
|
|
confidences = valid_detections[:, 4]
|
|
classes = valid_detections[:, 5].astype(int)
|
|
|
|
# Group by class
|
|
class_counts = {}
|
|
for cls_id in classes:
|
|
class_counts[cls_id] = class_counts.get(cls_id, 0) + 1
|
|
|
|
print(f" Classes found: {sorted(class_counts.keys())}")
|
|
|
|
# Focus on shiny icon (class 50)
|
|
shiny_mask = classes == 50
|
|
shiny_detections = valid_detections[shiny_mask]
|
|
|
|
if len(shiny_detections) > 0:
|
|
print(f"\n✨ SHINY ICON DETECTIONS (Class 50): {len(shiny_detections)}")
|
|
for i, det in enumerate(shiny_detections):
|
|
print(f" Detection {i}: conf={det[4]:.6f}, box=[{det[0]:.1f},{det[1]:.1f},{det[2]:.1f},{det[3]:.1f}]")
|
|
else:
|
|
print(f"\n❌ NO SHINY ICON DETECTIONS (Class 50)")
|
|
|
|
# Show high confidence detections
|
|
high_conf_mask = confidences > 0.1
|
|
high_conf_detections = valid_detections[high_conf_mask]
|
|
|
|
if len(high_conf_detections) > 0:
|
|
print(f"\n🎯 High confidence detections (>0.1): {len(high_conf_detections)}")
|
|
for i, det in enumerate(high_conf_detections[:10]): # Show top 10
|
|
print(f" Class {int(det[5])}: conf={det[4]:.6f}, box=[{det[0]:.1f},{det[1]:.1f},{det[2]:.1f},{det[3]:.1f}]")
|
|
|
|
return valid_detections
|
|
|
|
elif num_values > 80: # Raw format: [x, y, w, h, obj, class0, class1, ...]
|
|
print(f" Format: Raw output ({num_values-5} classes)")
|
|
|
|
# This would need more complex processing for raw outputs
|
|
print(f" ⚠️ Raw format detected - would need objectness * class confidence processing")
|
|
return None
|
|
|
|
else:
|
|
print(f" ⚠️ Unexpected output shape: {detections.shape}")
|
|
return None
|
|
|
|
def compare_models(pt_model_path, onnx_model_path, test_image_path):
|
|
"""Compare .pt and ONNX model outputs on the same image"""
|
|
print("="*80)
|
|
print("🔍 MODEL COMPARISON DEBUG SESSION")
|
|
print("="*80)
|
|
|
|
# Check if files exist
|
|
for path, name in [(pt_model_path, ".pt model"), (onnx_model_path, "ONNX model"), (test_image_path, "test image")]:
|
|
if not Path(path).exists():
|
|
print(f"❌ {name} not found: {path}")
|
|
return
|
|
|
|
# Preprocess image for ONNX
|
|
try:
|
|
preprocessed_img, original_img, ratio, pad = preprocess_image(test_image_path)
|
|
except Exception as e:
|
|
print(f"❌ Failed to preprocess image: {e}")
|
|
return
|
|
|
|
# Run .pt model
|
|
try:
|
|
pt_boxes, pt_confidences, pt_classes = run_pt_model(pt_model_path, test_image_path)
|
|
except Exception as e:
|
|
print(f"❌ Failed to run .pt model: {e}")
|
|
pt_boxes, pt_confidences, pt_classes = None, None, None
|
|
|
|
# Run ONNX model
|
|
try:
|
|
onnx_detections = run_onnx_model(onnx_model_path, preprocessed_img)
|
|
except Exception as e:
|
|
print(f"❌ Failed to run ONNX model: {e}")
|
|
onnx_detections = None
|
|
|
|
# Compare results
|
|
print("\n" + "="*80)
|
|
print("📊 COMPARISON SUMMARY")
|
|
print("="*80)
|
|
|
|
# Count shiny detections
|
|
pt_shiny_count = 0
|
|
onnx_shiny_count = 0
|
|
|
|
if pt_classes is not None:
|
|
pt_shiny_count = np.sum(pt_classes == 50)
|
|
|
|
if onnx_detections is not None and len(onnx_detections) > 0:
|
|
if onnx_detections.shape[1] == 6: # NMS format
|
|
onnx_classes = onnx_detections[:, 5].astype(int)
|
|
onnx_shiny_count = np.sum(onnx_classes == 50)
|
|
|
|
print(f"🔥 .pt Model Results:")
|
|
print(f" Total detections: {len(pt_boxes) if pt_boxes is not None else 0}")
|
|
print(f" Shiny icons (class 50): {pt_shiny_count}")
|
|
|
|
print(f"\n🔧 ONNX Model Results:")
|
|
print(f" Total detections: {len(onnx_detections) if onnx_detections is not None else 0}")
|
|
print(f" Shiny icons (class 50): {onnx_shiny_count}")
|
|
|
|
if pt_shiny_count > 0 and onnx_shiny_count == 0:
|
|
print(f"\n🚨 ISSUE CONFIRMED: .pt model finds {pt_shiny_count} shiny icons, ONNX finds 0")
|
|
print(f" This confirms the preprocessing/inference discrepancy")
|
|
elif pt_shiny_count == onnx_shiny_count and pt_shiny_count > 0:
|
|
print(f"\n✅ Both models find {pt_shiny_count} shiny icons - issue may be elsewhere")
|
|
|
|
print("\n" + "="*80)
|
|
|
|
if __name__ == "__main__":
|
|
# Test with available models and image
|
|
pt_model = "raw_models/best.pt"
|
|
|
|
# Test multiple ONNX variants
|
|
onnx_models = [
|
|
"app/src/main/assets/best.onnx",
|
|
"raw_models/exports/best_no_nms.onnx",
|
|
"raw_models/exports/best_nms_relaxed.onnx",
|
|
"raw_models/exports/best_nms_very_relaxed.onnx"
|
|
]
|
|
|
|
# You'll need to provide a test image with known shiny icon
|
|
test_image = "test_images/shiny_test.jpg" # Replace with actual test image path
|
|
|
|
print("🔍 Looking for test images...")
|
|
|
|
# Try to find a suitable test image
|
|
test_image_candidates = [
|
|
"test_images/shiny_test.jpg",
|
|
"test_images/test.jpg",
|
|
"screenshots/shiny.jpg",
|
|
"screenshots/test.png"
|
|
]
|
|
|
|
test_image_found = None
|
|
for candidate in test_image_candidates:
|
|
if Path(candidate).exists():
|
|
test_image_found = candidate
|
|
print(f" Found test image: {candidate}")
|
|
break
|
|
|
|
if not test_image_found:
|
|
print("❌ No test image found. Please provide a test image with shiny icon at one of these paths:")
|
|
for candidate in test_image_candidates:
|
|
print(f" {candidate}")
|
|
print("\nYou can capture a screenshot with shiny icon and save it as test_images/shiny_test.jpg")
|
|
exit(1)
|
|
|
|
# Run comparison for each ONNX model
|
|
for onnx_model in onnx_models:
|
|
if Path(onnx_model).exists():
|
|
print(f"\n🔄 Testing ONNX model: {onnx_model}")
|
|
compare_models(pt_model, onnx_model, test_image_found)
|
|
print("\n" + "="*120 + "\n")
|
|
else:
|
|
print(f"⚠️ ONNX model not found: {onnx_model}")
|