0xarchit commited on
Commit
663a37f
·
1 Parent(s): 0217894

added yolov8s vision model training notebook

Browse files
Files changed (2) hide show
  1. .gitignore +3 -1
  2. ModelTrain/main.ipynb +665 -0
.gitignore CHANGED
@@ -1,3 +1,5 @@
1
  **/*.env
2
  **/__pycache__/
3
- *.pyc
 
 
 
1
  **/*.env
2
  **/__pycache__/
3
+ *.pyc
4
+ *.jpg
5
+ *.png
ModelTrain/main.ipynb ADDED
@@ -0,0 +1,665 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": null,
6
+ "id": "1370c868",
7
+ "metadata": {},
8
+ "outputs": [],
9
+ "source": [
10
+ "\"\"\"\n",
11
+ "================================================================================\n",
12
+ "YOLO MODEL TRAINING PIPELINE FOR URBAN ISSUES DETECTION\n",
13
+ "================================================================================\n",
14
+ "Autonomous City Issue Resolution Agent\n",
15
+ "\n",
16
+ "This file is divided into sections. Copy each section into a separate \n",
17
+ "Jupyter Notebook cell and run sequentially.\n",
18
+ "\n",
19
+ "Classes:\n",
20
+ " 0: Damaged Road Issues\n",
21
+ " 1: Pothole Issues \n",
22
+ " 2: Illegal Parking Issues\n",
23
+ " 3: Broken Road Sign Issues\n",
24
+ " 4: Fallen Trees\n",
25
+ " 5: Littering/Garbage on Public Places\n",
26
+ " 6: Vandalism Issues\n",
27
+ " 7: Dead Animal Pollution\n",
28
+ " 8: Damaged Concrete Structures\n",
29
+ " 9: Damaged Electric Wires and Poles\n",
30
+ "================================================================================\n",
31
+ "\"\"\"\n",
32
+ "\n",
33
+ "\n",
34
+ "\"\"\"\n",
35
+ "================================================================================\n",
36
+ "SECTION 1: IMPORTS AND SETUP\n",
37
+ "================================================================================\n",
38
+ "\"\"\"\n",
39
+ "import os\n",
40
+ "import shutil\n",
41
+ "import yaml\n",
42
+ "import random\n",
43
+ "from pathlib import Path\n",
44
+ "from tqdm import tqdm\n",
45
+ "import matplotlib.pyplot as plt\n",
46
+ "import numpy as np\n",
47
+ "import cv2\n",
48
+ "from PIL import Image\n",
49
+ "import pandas as pd\n",
50
+ "import seaborn as sns\n",
51
+ "from ultralytics import YOLO\n",
52
+ "import torch\n",
53
+ "\n",
54
+ "BASE_DIR = Path(r\"D:\\B.Tech\\ProjeX\\24.HackTheThrone\")\n",
55
+ "DATASET_DIR = BASE_DIR / \"Dataset\"\n",
56
+ "MODEL_DIR = BASE_DIR / \"Model\"\n",
57
+ "MERGED_DIR = BASE_DIR / \"Dataset_Merged\"\n",
58
+ "IMG_SIZE = 640\n",
59
+ "\n",
60
+ "print(f\"PyTorch version: {torch.__version__}\")\n",
61
+ "print(f\"CUDA available: {torch.cuda.is_available()}\")\n",
62
+ "print(f\"CUDA version: {torch.version.cuda}\")\n",
63
+ "\n",
64
+ "if torch.cuda.is_available():\n",
65
+ " print(f\"GPU: {torch.cuda.get_device_name(0)}\")\n",
66
+ " print(f\"GPU Memory: {torch.cuda.get_device_properties(0).total_memory / 1024**3:.1f} GB\")\n",
67
+ " DEVICE = 0\n",
68
+ "else:\n",
69
+ " print(\"\\n[!] GPU NOT DETECTED - Training will use CPU (SLOW)\")\n",
70
+ " print(\" To install PyTorch 2.9.0 with CUDA 13.0 (Windows/Linux):\")\n",
71
+ " print(\" pip uninstall torch torchvision torchaudio -y\")\n",
72
+ " print(\" pip cache purge\")\n",
73
+ " print(\" pip install torch==2.9.0 torchvision==0.24.0 torchaudio==2.9.0 --index-url https://download.pytorch.org/whl/cu130\")\n",
74
+ " print(\" (For CPU-only or other CUDA builds like cu126/cu128, see https://pytorch.org/get-started/locally/)\")\n",
75
+ " DEVICE = \"cpu\"\n",
76
+ "\n",
77
+ "print(f\"\\nBase directory: {BASE_DIR}\")\n",
78
+ "print(f\"Device: {DEVICE}\")\n",
79
+ "\n"
80
+ ]
81
+ },
82
+ {
83
+ "cell_type": "code",
84
+ "execution_count": null,
85
+ "id": "d32f748e",
86
+ "metadata": {},
87
+ "outputs": [],
88
+ "source": [
89
+ "\"\"\"\n",
90
+ "================================================================================\n",
91
+ "SECTION 2: DATASET CONFIGURATION\n",
92
+ "================================================================================\n",
93
+ "\"\"\"\n",
94
+ "CLASS_NAMES = {\n",
95
+ " 0: \"Damaged Road Issues\",\n",
96
+ " 1: \"Pothole Issues\",\n",
97
+ " 2: \"Illegal Parking Issues\",\n",
98
+ " 3: \"Broken Road Sign Issues\",\n",
99
+ " 4: \"Fallen Trees\",\n",
100
+ " 5: \"Littering/Garbage on Public Places\",\n",
101
+ " 6: \"Vandalism Issues\",\n",
102
+ " 7: \"Dead Animal Pollution\",\n",
103
+ " 8: \"Damaged Concrete Structures\",\n",
104
+ " 9: \"Damaged Electric Wires and Poles\"\n",
105
+ "}\n",
106
+ "\n",
107
+ "DATASET_MAPPING = {\n",
108
+ " \"Potholes and RoadCracks/Potholes and RoadCracks\": {\n",
109
+ " \"class_map\": {1: 1},\n",
110
+ " \"name\": \"Potholes\"\n",
111
+ " },\n",
112
+ " \"Garbage/Garbage\": {\n",
113
+ " \"class_map\": {5: 5},\n",
114
+ " \"name\": \"Garbage\"\n",
115
+ " },\n",
116
+ " \"FallenTrees/FallenTrees\": {\n",
117
+ " \"class_map\": {4: 4},\n",
118
+ " \"name\": \"FallenTrees\"\n",
119
+ " },\n",
120
+ " \"DamagedElectricalPoles/DamagedElectricalPoles\": {\n",
121
+ " \"class_map\": {9: 9},\n",
122
+ " \"name\": \"DamagedElectricalPoles\"\n",
123
+ " },\n",
124
+ " \"Damaged concrete structures/Damaged concrete structures\": {\n",
125
+ " \"class_map\": {8: 8},\n",
126
+ " \"name\": \"DamagedConcrete\"\n",
127
+ " },\n",
128
+ " \"DamagedRoadSigns/DamagedRoadSigns\": {\n",
129
+ " \"class_map\": {0: 3, 1: 3},\n",
130
+ " \"name\": \"DamagedRoadSigns\"\n",
131
+ " },\n",
132
+ " \"DeadAnimalsPollution/DeadAnimalsPollution\": {\n",
133
+ " \"class_map\": {7: 7},\n",
134
+ " \"name\": \"DeadAnimals\"\n",
135
+ " },\n",
136
+ " \"Graffitti/Graffitti\": {\n",
137
+ " \"class_map\": {6: 6},\n",
138
+ " \"name\": \"Graffiti\"\n",
139
+ " },\n",
140
+ " \"IllegalParking/IllegalParking\": {\n",
141
+ " \"class_map\": {0: 2, 1: 2, 2: 2},\n",
142
+ " \"name\": \"IllegalParking\"\n",
143
+ " }\n",
144
+ "}\n",
145
+ "\n",
146
+ "print(f\"Total classes: {len(CLASS_NAMES)}\")\n",
147
+ "for idx, name in CLASS_NAMES.items():\n",
148
+ " print(f\" {idx}: {name}\")\n"
149
+ ]
150
+ },
151
+ {
152
+ "cell_type": "code",
153
+ "execution_count": null,
154
+ "id": "756043d6",
155
+ "metadata": {},
156
+ "outputs": [],
157
+ "source": [
158
+ "\"\"\"\n",
159
+ "================================================================================\n",
160
+ "SECTION 3: CREATE MERGED DATASET DIRECTORY STRUCTURE\n",
161
+ "================================================================================\n",
162
+ "\"\"\"\n",
163
+ "def create_merged_dataset_structure():\n",
164
+ " for split in [\"train\", \"valid\", \"test\"]:\n",
165
+ " (MERGED_DIR / \"images\" / split).mkdir(parents=True, exist_ok=True)\n",
166
+ " (MERGED_DIR / \"labels\" / split).mkdir(parents=True, exist_ok=True)\n",
167
+ " print(f\"Created merged dataset structure at: {MERGED_DIR}\")\n",
168
+ "\n",
169
+ "create_merged_dataset_structure()"
170
+ ]
171
+ },
172
+ {
173
+ "cell_type": "code",
174
+ "execution_count": null,
175
+ "id": "f33064a9",
176
+ "metadata": {},
177
+ "outputs": [],
178
+ "source": [
179
+ "\"\"\"\n",
180
+ "================================================================================\n",
181
+ "SECTION 4: MERGE AND RELABEL DATASETS\n",
182
+ "================================================================================\n",
183
+ "\"\"\"\n",
184
+ "def relabel_annotation(label_path, class_map, is_segmentation=False):\n",
185
+ " if not label_path.exists():\n",
186
+ " return None\n",
187
+ " \n",
188
+ " with open(label_path, 'r') as f:\n",
189
+ " lines = f.readlines()\n",
190
+ " \n",
191
+ " new_lines = []\n",
192
+ " for line in lines:\n",
193
+ " line = line.strip()\n",
194
+ " if not line:\n",
195
+ " continue\n",
196
+ " \n",
197
+ " parts = line.split()\n",
198
+ " if len(parts) < 5:\n",
199
+ " continue\n",
200
+ " \n",
201
+ " old_class = int(parts[0])\n",
202
+ " if old_class not in class_map:\n",
203
+ " continue\n",
204
+ " \n",
205
+ " new_class = class_map[old_class]\n",
206
+ " \n",
207
+ " if len(parts) == 5:\n",
208
+ " cx, cy, bw, bh = map(float, parts[1:5])\n",
209
+ " else:\n",
210
+ " coords = list(map(float, parts[1:]))\n",
211
+ " xs = coords[0::2]\n",
212
+ " ys = coords[1::2]\n",
213
+ " x_min, x_max = min(xs), max(xs)\n",
214
+ " y_min, y_max = min(ys), max(ys)\n",
215
+ " cx = (x_min + x_max) / 2.0\n",
216
+ " cy = (y_min + y_max) / 2.0\n",
217
+ " bw = x_max - x_min\n",
218
+ " bh = y_max - y_min\n",
219
+ " \n",
220
+ " new_line = f\"{new_class} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}\"\n",
221
+ " new_lines.append(new_line)\n",
222
+ " \n",
223
+ " return new_lines if new_lines else None\n",
224
+ "\n",
225
+ "def merge_datasets():\n",
226
+ " stats = {split: {cls: 0 for cls in CLASS_NAMES.keys()} for split in [\"train\", \"valid\", \"test\"]}\n",
227
+ " \n",
228
+ " for dataset_path, config in tqdm(DATASET_MAPPING.items(), desc=\"Merging datasets\"):\n",
229
+ " dataset_full_path = DATASET_DIR / dataset_path\n",
230
+ " class_map = config[\"class_map\"]\n",
231
+ " dataset_name = config[\"name\"]\n",
232
+ " \n",
233
+ " for split in [\"train\", \"valid\", \"test\"]:\n",
234
+ " images_dir = dataset_full_path / split / \"images\"\n",
235
+ " labels_dir = dataset_full_path / split / \"labels\"\n",
236
+ " \n",
237
+ " if not images_dir.exists():\n",
238
+ " print(f\"Skipping {dataset_name}/{split} - images not found\")\n",
239
+ " continue\n",
240
+ " \n",
241
+ " image_files = list(images_dir.glob(\"*.[jJ][pP][gG]\")) + \\\n",
242
+ " list(images_dir.glob(\"*.[jJ][pP][eE][gG]\")) + \\\n",
243
+ " list(images_dir.glob(\"*.[pP][nN][gG]\"))\n",
244
+ " \n",
245
+ " for img_path in image_files:\n",
246
+ " label_name = img_path.stem + \".txt\"\n",
247
+ " label_path = labels_dir / label_name\n",
248
+ " \n",
249
+ " new_lines = relabel_annotation(label_path, class_map)\n",
250
+ " if new_lines is None:\n",
251
+ " continue\n",
252
+ " \n",
253
+ " new_img_name = f\"{dataset_name}_{img_path.name}\"\n",
254
+ " new_label_name = f\"{dataset_name}_{img_path.stem}.txt\"\n",
255
+ " \n",
256
+ " dst_img = MERGED_DIR / \"images\" / split / new_img_name\n",
257
+ " dst_label = MERGED_DIR / \"labels\" / split / new_label_name\n",
258
+ " \n",
259
+ " shutil.copy2(img_path, dst_img)\n",
260
+ " \n",
261
+ " with open(dst_label, 'w') as f:\n",
262
+ " f.write('\\n'.join(new_lines))\n",
263
+ " \n",
264
+ " for line in new_lines:\n",
265
+ " cls = int(line.split()[0])\n",
266
+ " stats[split][cls] += 1\n",
267
+ " \n",
268
+ " return stats\n",
269
+ "\n",
270
+ "def merged_dataset_exists():\n",
271
+ " for split in [\"train\", \"valid\", \"test\"]:\n",
272
+ " images_dir = MERGED_DIR / \"images\" / split\n",
273
+ " labels_dir = MERGED_DIR / \"labels\" / split\n",
274
+ " if not images_dir.exists() or not labels_dir.exists():\n",
275
+ " return False\n",
276
+ " if not list(images_dir.glob(\"*\")) or not list(labels_dir.glob(\"*.txt\")):\n",
277
+ " return False\n",
278
+ " return True\n",
279
+ "\n",
280
+ "if merged_dataset_exists():\n",
281
+ " print(f\"Merged dataset already exists at: {MERGED_DIR}\")\n",
282
+ " print(\"Skipping dataset merge.\")\n",
283
+ " stats = None\n",
284
+ "else:\n",
285
+ " print(\"Merging datasets...\")\n",
286
+ " stats = merge_datasets()\n",
287
+ " \n",
288
+ " print(\"\\n Dataset Statistics:\")\n",
289
+ " for split, class_stats in stats.items():\n",
290
+ " total = sum(class_stats.values())\n",
291
+ " print(f\"\\n{split.upper()}: {total} annotations\")\n",
292
+ " for cls, count in class_stats.items():\n",
293
+ " if count > 0:\n",
294
+ " print(f\" {cls}: {CLASS_NAMES[cls]} - {count}\")\n",
295
+ "\n"
296
+ ]
297
+ },
298
+ {
299
+ "cell_type": "code",
300
+ "execution_count": null,
301
+ "id": "71e9fa04",
302
+ "metadata": {},
303
+ "outputs": [],
304
+ "source": [
305
+ "\"\"\"\n",
306
+ "================================================================================\n",
307
+ "SECTION 5: GENERATE DATA.YAML CONFIG FILE\n",
308
+ "================================================================================\n",
309
+ "\"\"\"\n",
310
+ "data_yaml = {\n",
311
+ " \"path\": str(MERGED_DIR),\n",
312
+ " \"train\": \"images/train\",\n",
313
+ " \"val\": \"images/valid\",\n",
314
+ " \"test\": \"images/test\",\n",
315
+ " \"nc\": len(CLASS_NAMES),\n",
316
+ " \"names\": list(CLASS_NAMES.values())\n",
317
+ "}\n",
318
+ "\n",
319
+ "yaml_path = MERGED_DIR / \"data.yaml\"\n",
320
+ "with open(yaml_path, 'w') as f:\n",
321
+ " yaml.dump(data_yaml, f, default_flow_style=False)\n",
322
+ "\n",
323
+ "print(f\"Created data.yaml at: {yaml_path}\")\n",
324
+ "print(\"\\nContents:\")\n",
325
+ "with open(yaml_path, 'r') as f:\n",
326
+ " print(f.read())\n",
327
+ "\n"
328
+ ]
329
+ },
330
+ {
331
+ "cell_type": "code",
332
+ "execution_count": null,
333
+ "id": "680070a4",
334
+ "metadata": {},
335
+ "outputs": [],
336
+ "source": [
337
+ "\"\"\"\n",
338
+ "================================================================================\n",
339
+ "SECTION 6: VALIDATE DATASET INTEGRITY\n",
340
+ "================================================================================\n",
341
+ "\"\"\"\n",
342
+ "def validate_dataset():\n",
343
+ " issues = []\n",
344
+ " \n",
345
+ " for split in [\"train\", \"valid\", \"test\"]:\n",
346
+ " images_dir = MERGED_DIR / \"images\" / split\n",
347
+ " labels_dir = MERGED_DIR / \"labels\" / split\n",
348
+ " \n",
349
+ " image_files = list(images_dir.glob(\"*\"))\n",
350
+ " label_files = list(labels_dir.glob(\"*.txt\"))\n",
351
+ " \n",
352
+ " image_stems = {f.stem for f in image_files}\n",
353
+ " label_stems = {f.stem for f in label_files}\n",
354
+ " \n",
355
+ " missing_labels = image_stems - label_stems\n",
356
+ " missing_images = label_stems - image_stems\n",
357
+ " \n",
358
+ " if missing_labels:\n",
359
+ " issues.append(f\"{split}: {len(missing_labels)} images missing labels\")\n",
360
+ " if missing_images:\n",
361
+ " issues.append(f\"{split}: {len(missing_images)} labels missing images\")\n",
362
+ " \n",
363
+ " print(f\"{split}: {len(image_files)} images, {len(label_files)} labels\")\n",
364
+ " \n",
365
+ " if issues:\n",
366
+ " print(\"\\n Issues found:\")\n",
367
+ " for issue in issues:\n",
368
+ " print(f\" - {issue}\")\n",
369
+ " else:\n",
370
+ " print(\"\\n All files validated successfully!\")\n",
371
+ " \n",
372
+ " return len(issues) == 0\n",
373
+ "\n",
374
+ "validate_dataset()\n",
375
+ "\n"
376
+ ]
377
+ },
378
+ {
379
+ "cell_type": "code",
380
+ "execution_count": null,
381
+ "id": "3e0466f7",
382
+ "metadata": {},
383
+ "outputs": [],
384
+ "source": [
385
+ "\n",
386
+ "\"\"\"\n",
387
+ "================================================================================\n",
388
+ "SECTION 7: VISUALIZE SAMPLE IMAGES WITH ANNOTATIONS\n",
389
+ "================================================================================\n",
390
+ "\"\"\"\n",
391
+ "def visualize_samples(n_samples=6):\n",
392
+ " train_images = list((MERGED_DIR / \"images\" / \"train\").glob(\"*\"))\n",
393
+ " sample_images = random.sample(train_images, min(n_samples, len(train_images)))\n",
394
+ " \n",
395
+ " fig, axes = plt.subplots(2, 3, figsize=(15, 10))\n",
396
+ " axes = axes.flatten()\n",
397
+ " \n",
398
+ " colors = plt.cm.tab10(np.linspace(0, 1, 10))\n",
399
+ " \n",
400
+ " for idx, img_path in enumerate(sample_images):\n",
401
+ " img = cv2.imread(str(img_path))\n",
402
+ " img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n",
403
+ " h, w = img.shape[:2]\n",
404
+ " \n",
405
+ " label_path = MERGED_DIR / \"labels\" / \"train\" / (img_path.stem + \".txt\")\n",
406
+ " \n",
407
+ " if label_path.exists():\n",
408
+ " with open(label_path, 'r') as f:\n",
409
+ " for line in f:\n",
410
+ " parts = line.strip().split()\n",
411
+ " if len(parts) >= 5:\n",
412
+ " cls = int(parts[0])\n",
413
+ " \n",
414
+ " if len(parts) == 5:\n",
415
+ " cx, cy, bw, bh = map(float, parts[1:5])\n",
416
+ " x1 = int((cx - bw/2) * w)\n",
417
+ " y1 = int((cy - bh/2) * h)\n",
418
+ " x2 = int((cx + bw/2) * w)\n",
419
+ " y2 = int((cy + bh/2) * h)\n",
420
+ " else:\n",
421
+ " coords = list(map(float, parts[1:]))\n",
422
+ " xs = [coords[i] * w for i in range(0, len(coords), 2)]\n",
423
+ " ys = [coords[i] * h for i in range(1, len(coords), 2)]\n",
424
+ " x1, x2 = int(min(xs)), int(max(xs))\n",
425
+ " y1, y2 = int(min(ys)), int(max(ys))\n",
426
+ " \n",
427
+ " color = tuple(int(c * 255) for c in colors[cls][:3])\n",
428
+ " cv2.rectangle(img, (x1, y1), (x2, y2), color, 2)\n",
429
+ " cv2.putText(img, CLASS_NAMES[cls][:15], (x1, y1-5), \n",
430
+ " cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 2)\n",
431
+ " \n",
432
+ " axes[idx].imshow(img)\n",
433
+ " axes[idx].set_title(img_path.name[:30])\n",
434
+ " axes[idx].axis('off')\n",
435
+ " \n",
436
+ " plt.tight_layout()\n",
437
+ " plt.savefig(MODEL_DIR / \"sample_annotations.png\", dpi=150)\n",
438
+ " plt.show()\n",
439
+ " print(f\"Saved sample visualization to: {MODEL_DIR / 'sample_annotations.png'}\")\n",
440
+ "\n",
441
+ "visualize_samples()\n"
442
+ ]
443
+ },
444
+ {
445
+ "cell_type": "code",
446
+ "execution_count": null,
447
+ "id": "8ebfcace",
448
+ "metadata": {},
449
+ "outputs": [],
450
+ "source": [
451
+ "\"\"\"\n",
452
+ "================================================================================\n",
453
+ "SECTION 9: EVALUATE MODEL ON VALIDATION SET\n",
454
+ "================================================================================\n",
455
+ "\"\"\"\n",
456
+ "best_model_path = MODEL_DIR / \"urban_issues_yolov8\" / \"weights\" / \"best.pt\"\n",
457
+ "model = YOLO(str(best_model_path))\n",
458
+ "\n",
459
+ "val_results = model.val(\n",
460
+ " data=str(MERGED_DIR / \"data.yaml\"),\n",
461
+ " split=\"val\",\n",
462
+ " imgsz=IMG_SIZE,\n",
463
+ " batch=32,\n",
464
+ " device=DEVICE,\n",
465
+ " workers=4,\n",
466
+ " save_json=True,\n",
467
+ " plots=True\n",
468
+ ")\n",
469
+ "\n",
470
+ "print(\"\\nValidation Results:\")\n",
471
+ "print(f\"mAP50: {val_results.box.map50:.4f}\")\n",
472
+ "print(f\"mAP50-95: {val_results.box.map:.4f}\")\n",
473
+ "print(f\"Precision: {val_results.box.mp:.4f}\")\n",
474
+ "print(f\"Recall: {val_results.box.mr:.4f}\")\n",
475
+ "\n",
476
+ "\n",
477
+ "\n"
478
+ ]
479
+ },
480
+ {
481
+ "cell_type": "code",
482
+ "execution_count": null,
483
+ "id": "86cb025d",
484
+ "metadata": {},
485
+ "outputs": [],
486
+ "source": [
487
+ "\"\"\"\n",
488
+ "================================================================================\n",
489
+ "SECTION 10: RUN INFERENCE ON TEST SET\n",
490
+ "================================================================================\n",
491
+ "\"\"\"\n",
492
+ "test_images_dir = MERGED_DIR / \"images\" / \"test\"\n",
493
+ "test_images = list(test_images_dir.glob(\"*\"))[:20]\n",
494
+ "\n",
495
+ "results = model.predict(\n",
496
+ " source=test_images,\n",
497
+ " save=True,\n",
498
+ " save_txt=True,\n",
499
+ " project=str(MODEL_DIR),\n",
500
+ " name=\"test_predictions\",\n",
501
+ " exist_ok=True,\n",
502
+ " conf=0.25,\n",
503
+ " iou=0.45\n",
504
+ ")\n",
505
+ "\n",
506
+ "print(f\"\\nTest predictions saved to: {MODEL_DIR / 'test_predictions'}\")\n",
507
+ "\n",
508
+ "for r in results[:3]:\n",
509
+ " print(f\"\\nImage: {Path(r.path).name}\")\n",
510
+ " if r.boxes is not None:\n",
511
+ " for box in r.boxes:\n",
512
+ " cls = int(box.cls[0])\n",
513
+ " conf = float(box.conf[0])\n",
514
+ " print(f\" - {CLASS_NAMES[cls]}: {conf:.2f}\")\n",
515
+ "\n",
516
+ "\n"
517
+ ]
518
+ },
519
+ {
520
+ "cell_type": "code",
521
+ "execution_count": null,
522
+ "id": "3e333a9d",
523
+ "metadata": {},
524
+ "outputs": [],
525
+ "source": [
526
+ "\"\"\"\n",
527
+ "================================================================================\n",
528
+ "SECTION 11: GENERATE CONFUSION MATRIX AND ANALYSIS\n",
529
+ "================================================================================\n",
530
+ "\"\"\"\n",
531
+ "confusion_matrix_path = MODEL_DIR / \"urban_issues_yolov8\" / \"confusion_matrix.png\"\n",
532
+ "if confusion_matrix_path.exists():\n",
533
+ " img = Image.open(confusion_matrix_path)\n",
534
+ " plt.figure(figsize=(12, 10))\n",
535
+ " plt.imshow(img)\n",
536
+ " plt.axis('off')\n",
537
+ " plt.title(\"Confusion Matrix\")\n",
538
+ " plt.tight_layout()\n",
539
+ " plt.show()\n",
540
+ "else:\n",
541
+ " print(\"Confusion matrix not found. Will be generated after training.\")\n",
542
+ "\n",
543
+ "results_csv = MODEL_DIR / \"urban_issues_yolov8\" / \"results.csv\"\n",
544
+ "if results_csv.exists():\n",
545
+ " df = pd.read_csv(results_csv)\n",
546
+ " df.columns = df.columns.str.strip()\n",
547
+ " \n",
548
+ " fig, axes = plt.subplots(2, 2, figsize=(14, 10))\n",
549
+ " \n",
550
+ " if 'train/box_loss' in df.columns:\n",
551
+ " axes[0, 0].plot(df['epoch'], df['train/box_loss'], label='Box Loss')\n",
552
+ " axes[0, 0].plot(df['epoch'], df['train/cls_loss'], label='Class Loss')\n",
553
+ " axes[0, 0].set_xlabel('Epoch')\n",
554
+ " axes[0, 0].set_ylabel('Loss')\n",
555
+ " axes[0, 0].set_title('Training Losses')\n",
556
+ " axes[0, 0].legend()\n",
557
+ " axes[0, 0].grid(True)\n",
558
+ " \n",
559
+ " if 'metrics/mAP50(B)' in df.columns:\n",
560
+ " axes[0, 1].plot(df['epoch'], df['metrics/mAP50(B)'], label='mAP50')\n",
561
+ " axes[0, 1].plot(df['epoch'], df['metrics/mAP50-95(B)'], label='mAP50-95')\n",
562
+ " axes[0, 1].set_xlabel('Epoch')\n",
563
+ " axes[0, 1].set_ylabel('mAP')\n",
564
+ " axes[0, 1].set_title('Validation mAP')\n",
565
+ " axes[0, 1].legend()\n",
566
+ " axes[0, 1].grid(True)\n",
567
+ " \n",
568
+ " if 'metrics/precision(B)' in df.columns:\n",
569
+ " axes[1, 0].plot(df['epoch'], df['metrics/precision(B)'], label='Precision')\n",
570
+ " axes[1, 0].plot(df['epoch'], df['metrics/recall(B)'], label='Recall')\n",
571
+ " axes[1, 0].set_xlabel('Epoch')\n",
572
+ " axes[1, 0].set_ylabel('Score')\n",
573
+ " axes[1, 0].set_title('Precision & Recall')\n",
574
+ " axes[1, 0].legend()\n",
575
+ " axes[1, 0].grid(True)\n",
576
+ " \n",
577
+ " if 'val/box_loss' in df.columns:\n",
578
+ " axes[1, 1].plot(df['epoch'], df['val/box_loss'], label='Val Box Loss')\n",
579
+ " axes[1, 1].plot(df['epoch'], df['val/cls_loss'], label='Val Class Loss')\n",
580
+ " axes[1, 1].set_xlabel('Epoch')\n",
581
+ " axes[1, 1].set_ylabel('Loss')\n",
582
+ " axes[1, 1].set_title('Validation Losses')\n",
583
+ " axes[1, 1].legend()\n",
584
+ " axes[1, 1].grid(True)\n",
585
+ " \n",
586
+ " plt.tight_layout()\n",
587
+ " plt.savefig(MODEL_DIR / \"training_metrics.png\", dpi=150)\n",
588
+ " plt.show()\n",
589
+ " print(f\"Saved training metrics to: {MODEL_DIR / 'training_metrics.png'}\")\n",
590
+ "\n",
591
+ "\n"
592
+ ]
593
+ },
594
+ {
595
+ "cell_type": "code",
596
+ "execution_count": null,
597
+ "id": "ad0f65a2",
598
+ "metadata": {},
599
+ "outputs": [],
600
+ "source": [
601
+ "\"\"\"\n",
602
+ "================================================================================\n",
603
+ "SECTION 12: EXPORT OPTIMIZED MODEL\n",
604
+ "================================================================================\n",
605
+ "\"\"\"\n",
606
+ "best_model = YOLO(str(best_model_path))\n",
607
+ "\n",
608
+ "onnx_path = best_model.export(format=\"onnx\", simplify=True, dynamic=False)\n",
609
+ "print(f\"Exported ONNX model to: {onnx_path}\")\n",
610
+ "\n",
611
+ "torchscript_path = best_model.export(format=\"torchscript\")\n",
612
+ "print(f\"Exported TorchScript model to: {torchscript_path}\")\n",
613
+ "\n",
614
+ "model_info = {\n",
615
+ " \"model_name\": \"Urban Issues YOLOv8 Detector\",\n",
616
+ " \"version\": \"1.0\",\n",
617
+ " \"classes\": CLASS_NAMES,\n",
618
+ " \"input_size\": IMG_SIZE,\n",
619
+ " \"best_weights\": str(best_model_path),\n",
620
+ " \"onnx_path\": str(onnx_path),\n",
621
+ " \"torchscript_path\": str(torchscript_path),\n",
622
+ " \"val_mAP50\": float(val_results.box.map50),\n",
623
+ " \"val_mAP50_95\": float(val_results.box.map)\n",
624
+ "}\n",
625
+ "\n",
626
+ "with open(MODEL_DIR / \"model_info.yaml\", 'w') as f:\n",
627
+ " yaml.dump(model_info, f, default_flow_style=False)\n",
628
+ "\n",
629
+ "print(f\"\\nModel info saved to: {MODEL_DIR / 'model_info.yaml'}\")\n",
630
+ "print(\"\\n\" + \"=\"*60)\n",
631
+ "print(\"TRAINING PIPELINE COMPLETE!\")\n",
632
+ "print(\"=\"*60)\n",
633
+ "print(f\"Best model: {best_model_path}\")\n",
634
+ "print(f\"ONNX export: {onnx_path}\")\n",
635
+ "print(f\"Validation mAP50: {val_results.box.map50:.4f}\")\n",
636
+ "\"\"\"\n",
637
+ "================================================================================\n",
638
+ "END OF PIPELINE\n",
639
+ "================================================================================\n",
640
+ "\"\"\"\n"
641
+ ]
642
+ }
643
+ ],
644
+ "metadata": {
645
+ "kernelspec": {
646
+ "display_name": ".venv",
647
+ "language": "python",
648
+ "name": "python3"
649
+ },
650
+ "language_info": {
651
+ "codemirror_mode": {
652
+ "name": "ipython",
653
+ "version": 3
654
+ },
655
+ "file_extension": ".py",
656
+ "mimetype": "text/x-python",
657
+ "name": "python",
658
+ "nbconvert_exporter": "python",
659
+ "pygments_lexer": "ipython3",
660
+ "version": "3.11.9"
661
+ }
662
+ },
663
+ "nbformat": 4,
664
+ "nbformat_minor": 5
665
+ }