Instructions to use Shadyemad/s24-apple-detector with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- ultralytics
How to use Shadyemad/s24-apple-detector with ultralytics:
# Couldn't find a valid YOLO version tag. # Replace XX with the correct version. from ultralytics import YOLOvXX model = YOLOvXX.from_pretrained("Shadyemad/s24-apple-detector") source = 'http://images.cocodataset.org/val2017/000000039769.jpg' model.predict(source=source, save=True) - Notebooks
- Google Colab
- Kaggle
| import cv2, json, os, sys, uuid, threading, time, base64 | |
| import numpy as np | |
| from ultralytics import YOLO | |
| from collections import defaultdict | |
| YOLO_MODEL = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'best.pt') | |
| TRACKER_CONFIG = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'bytetrack.yaml') | |
| CONF_THRESH = 0.25 | |
| IOU_THRESH = 0.45 | |
| MAX_ABSENT_FRAMES = 15 | |
| SIMILARITY_IOU = 0.35 | |
| _model_lock = threading.Lock() | |
| _model_cache = None | |
| def _get_model(): | |
| global _model_cache | |
| if _model_cache is None: | |
| _model_cache = YOLO(YOLO_MODEL) | |
| return _model_cache | |
| def _ensure_tracker_config(): | |
| path = TRACKER_CONFIG | |
| if not os.path.exists(path): | |
| with open(path, 'w') as f: | |
| f.write("""# ByteTrack | |
| tracker_type: bytetrack | |
| track_high_thresh: 0.25 | |
| track_low_thresh: 0.1 | |
| new_track_thresh: 0.6 | |
| track_buffer: 30 | |
| match_thresh: 0.8 | |
| fuse_score: True | |
| # BoT-SORT (uncomment to use instead) | |
| # tracker_type: botsort | |
| # track_high_thresh: 0.25 | |
| # track_low_thresh: 0.1 | |
| # new_track_thresh: 0.5 | |
| # track_buffer: 50 | |
| # match_thresh: 0.8 | |
| # fuse_score: True | |
| # gmc_method: sparseOptFlow | |
| """) | |
| return path | |
| class AppleTracker: | |
| def __init__(self, tracker_type='bytetrack'): | |
| self.model = _get_model() | |
| self.tracker_type = tracker_type | |
| self.tracker_cfg = _ensure_tracker_config() | |
| self.apple_tracks = {} | |
| self.next_id = 1 | |
| self.frame_count = 0 | |
| self.completed_tracks = set() | |
| def _get_label(self, cls_id): | |
| APPLE_CLASSES = {0: 'apple'} | |
| return APPLE_CLASSES.get(int(cls_id), 'apple') | |
| def process_frame(self, frame): | |
| h, w = frame.shape[:2] | |
| self.frame_count += 1 | |
| results = self.model.track( | |
| frame, | |
| persist=True, | |
| conf=CONF_THRESH, | |
| iou=IOU_THRESH, | |
| tracker=self.tracker_cfg, | |
| verbose=False, | |
| device='cpu' | |
| ) | |
| current_ids = set() | |
| detections = [] | |
| if results and len(results) > 0: | |
| r = results[0] | |
| if r.boxes is not None and r.boxes.id is not None: | |
| for i, box in enumerate(r.boxes): | |
| track_id = int(box.id[i].item()) | |
| conf = float(box.conf[i].item()) | |
| cls = int(box.cls[i].item()) | |
| x1, y1, x2, y2 = box.xyxy[i].tolist() | |
| x1, y1, x2, y2 = int(x1), int(y1), int(x2), int(y2) | |
| bw, bh = x2 - x1, y2 - y1 | |
| cx, cy = x1 + bw // 2, y1 + bh // 2 | |
| radius = max(bw, bh) // 2 | |
| current_ids.add(track_id) | |
| detections.append({ | |
| 'frame': self.frame_count, | |
| 'track_id': track_id, | |
| 'bbox': [x1, y1, x2, y2], | |
| 'center': [cx, cy], | |
| 'radius': radius, | |
| 'confidence': round(conf * 100, 1), | |
| }) | |
| if track_id not in self.apple_tracks: | |
| self.apple_tracks[track_id] = { | |
| 'track_id': track_id, | |
| 'first_frame': self.frame_count, | |
| 'last_frame': self.frame_count, | |
| 'total_frames': 1, | |
| 'max_confidence': conf, | |
| 'bboxes': [detections[-1]], | |
| 'absent_frames': 0, | |
| } | |
| else: | |
| t = self.apple_tracks[track_id] | |
| t['last_frame'] = self.frame_count | |
| t['total_frames'] += 1 | |
| t['absent_frames'] = 0 | |
| if conf > t['max_confidence']: | |
| t['max_confidence'] = conf | |
| if len(t['bboxes']) < 60: | |
| t['bboxes'].append(detections[-1]) | |
| else: | |
| t['bboxes'][-1] = detections[-1] | |
| for tid in list(self.apple_tracks.keys()): | |
| if tid not in current_ids: | |
| t = self.apple_tracks[tid] | |
| t['absent_frames'] += 1 | |
| if t['absent_frames'] >= MAX_ABSENT_FRAMES: | |
| if tid not in self.completed_tracks: | |
| self.completed_tracks.add(tid) | |
| return detections, current_ids | |
| def get_summary(self): | |
| active = {tid: t for tid, t in self.apple_tracks.items() | |
| if tid not in self.completed_tracks and t['absent_frames'] < 5} | |
| completed_ids = self.completed_tracks | { | |
| tid for tid, t in self.apple_tracks.items() | |
| if t['absent_frames'] >= 5 | |
| } | |
| valid_tracks = [t for tid, t in self.apple_tracks.items() if tid in completed_ids] | |
| total_unique = len(valid_tracks) | |
| if total_unique == 0: | |
| total_unique = len(self.apple_tracks) | |
| avg_conf = np.mean([t['max_confidence'] for t in self.apple_tracks.values()]) * 100 if self.apple_tracks else 0 | |
| return { | |
| 'total_unique_apples': total_unique, | |
| 'active_tracks': len(active), | |
| 'completed_tracks': len(completed_ids), | |
| 'total_frames_processed': self.frame_count, | |
| 'average_confidence': round(avg_conf, 1), | |
| 'tracks': [ | |
| { | |
| 'track_id': t['track_id'], | |
| 'first_frame': t['first_frame'], | |
| 'last_frame': t['last_frame'], | |
| 'total_frames': t['total_frames'], | |
| 'max_confidence': round(t['max_confidence'] * 100, 1), | |
| 'status': 'completed' if tid in completed_ids else 'active', | |
| } | |
| for tid, t in self.apple_tracks.items() | |
| ], | |
| } | |
| def reset(self): | |
| self.apple_tracks.clear() | |
| self.completed_tracks.clear() | |
| self.next_id = 1 | |
| self.frame_count = 0 | |
| def create_tracked_annotation(frame, detections, active_ids, completed_count): | |
| annotated = frame.copy() | |
| h, w = frame.shape[:2] | |
| font = cv2.FONT_HERSHEY_SIMPLEX | |
| font_scale = max(0.5, min(h, w) / 1500.0) | |
| thickness = max(2, int(font_scale * 2.5)) | |
| line_w = max(3, int(min(h, w) / 300)) | |
| for det in detections: | |
| tid = det['track_id'] | |
| x1, y1, x2, y2 = det['bbox'] | |
| color = (0, 200, 0) if tid in active_ids else (100, 100, 100) | |
| cv2.rectangle(annotated, (x1, y1), (x2, y2), color, line_w) | |
| label = f"#{tid}" | |
| (tw, th), _ = cv2.getTextSize(label, font, font_scale, thickness) | |
| pad = 4 | |
| ly = y1 - 8 if y1 - th - pad > 0 else y2 + th + pad | |
| cv2.rectangle(annotated, (x1, ly - th - pad), (x1 + tw + pad * 2, ly + pad), (0, 0, 0), -1) | |
| cv2.putText(annotated, label, (x1 + pad, ly), font, font_scale, (255, 255, 255), thickness, cv2.LINE_AA) | |
| status = f"Frame: {detections[0]['frame'] if detections else 0} | Active: {len(active_ids)} | Total unique: {completed_count}" | |
| (stw, sth), _ = cv2.getTextSize(status, font, font_scale * 1.1, thickness) | |
| bar_h = sth + 16 | |
| cv2.rectangle(annotated, (0, 0), (stw + 24, bar_h), (0, 0, 0), -1) | |
| cv2.putText(annotated, status, (12, sth + 8), font, font_scale * 1.1, (0, 255, 0), thickness, cv2.LINE_AA) | |
| return annotated | |
| def process_video_file(video_path, max_frames=0, progress_callback=None): | |
| cap = cv2.VideoCapture(video_path) | |
| if not cap.isOpened(): | |
| raise ValueError(f"Cannot open video: {video_path}") | |
| fps = cap.get(cv2.CAP_PROP_FPS) | |
| total_frames = int(cap.get(cv2.CAP_PROP_FRAMES)) | |
| if max_frames > 0: | |
| total_frames = min(total_frames, max_frames) | |
| tracker = AppleTracker() | |
| annotated_frames = [] | |
| frame_idx = 0 | |
| while True: | |
| if max_frames > 0 and frame_idx >= max_frames: | |
| break | |
| ret, frame = cap.read() | |
| if not ret: | |
| break | |
| detections, active_ids = tracker.process_frame(frame) | |
| if frame_idx % max(1, int(fps / 3)) == 0 or len(detections) > 0: | |
| annotated = create_tracked_annotation( | |
| frame, detections, active_ids, | |
| len(tracker.completed_tracks) | |
| ) | |
| _, buffer = cv2.imencode('.jpg', annotated, [cv2.IMWRITE_JPEG_QUALITY, 85]) | |
| annotated_b64 = base64.b64encode(buffer).decode('utf-8') | |
| annotated_frames.append({ | |
| 'frame': frame_idx + 1, | |
| 'active_tracks': len(active_ids), | |
| 'detections': len(detections), | |
| 'annotated_base64': annotated_b64, | |
| }) | |
| if len(annotated_frames) > 20: | |
| annotated_frames.pop(0) | |
| frame_idx += 1 | |
| if progress_callback and frame_idx % max(1, int(total_frames / 10)) == 0: | |
| progress_callback(int(frame_idx / total_frames * 100)) | |
| cap.release() | |
| summary = tracker.get_summary() | |
| return { | |
| 'summary': summary, | |
| 'fps': fps, | |
| 'total_frames': frame_idx, | |
| 'annotated_previews': annotated_frames, | |
| } | |