First, I apologize. In a recent post, I mentioned that YOLO could achieve an LB of 0.80, which was based on my past experience and intuition. However, the reality is not entirely clear. The approach below is a mid-level one—not particularly good or bad—and it resulted in an LB of almost 1.10. That said, there is plenty of room for improvement.
This part took me many tries to just make it working .
I selected YOLOv8x (extra-large variant) for its balance of accuracy and computational efficiency. I configured the model just average perametrs
Due to memort constraint i selected some perameters (i am not proud of) .
The main challenge was the images with numerous panels (up to 15 or more ) annotated by a single polygon.
During inference, I:
An example of a polygon parser to handle various annotation formats and potential corruption in the data:
def parse_polygon(poly_str):
"""Safe polygon parsing with multiple validation layers"""
try:
parsed = ast.literal_eval(str(poly_str))
if not isinstance(parsed, list):
return []
valid_poly = []
for item in parsed:
if isinstance(item, (list, tuple)) and len(item) >= 2:
try:
x = float(item[0])
y = float(item[1])
valid_poly.append([x, y])
except:
continue
return valid_poly
except:
return []
An example of a function to convert polygon annotations to YOLO-compatible bounding boxes:
def create_yolo_labels(df, mode='train'):
print(f"\n📋 Creating YOLO labels for {mode} data...")
missing_count = 0
processed_ids = []
grouped = df.groupby('ID', sort=False)
for img_id, group in tqdm(grouped, total=len(grouped), desc=f"Processing {mode} images"):
img_path = f"{CFG.data_path}/images/{img_id}.jpg"
label_path = f"{CFG.work_dir}/{mode}/labels/{img_id}.txt"
if not os.path.exists(img_path):
missing_count += 1
continue
try:
img = cv2.imread(img_path)
h, w = img.shape[:2]
except Exception as e:
print(f"⚠️ Corrupt image {img_id}: {str(e)}")
missing_count += 1
continue
valid_boxes = []
for _, row in group.iterrows():
try:
boil_count = int(row['boil_nbr'])
pan_count = int(row['pan_nbr'])
except KeyError:
print(f"⚠️ Missing target columns in {img_id}")
continue
polygon_points = parse_polygon(row['polygon'])
if not polygon_points:
continue
try:
points = np.array(polygon_points, dtype=np.float32)
# Handle coordinate types
if np.max(points) <= 1.0: # Normalized coordinates
x_coords = points[:, 0] * w
y_coords = points[:, 1] * h
else: # Absolute coordinates
x_coords = points[:, 0]
y_coords = points[:, 1]
# Calculate bounding box from all polygon points
x_min = max(0, np.min(x_coords))
x_max = min(w, np.max(x_coords))
y_min = max(0, np.min(y_coords))
y_max = min(h, np.max(y_coords))
if x_max <= x_min or y_max <= y_min:
continue
# Convert to YOLO format
x_center = ((x_min + x_max) / 2) / w
y_center = ((y_min + y_max) / 2) / h
bbox_w = (x_max - x_min) / w
bbox_h = (y_max - y_min) / h
# Create boxes for each target type
if boil_count > 0:
valid_boxes.append(f"0 {x_center:.6f} {y_center:.6f} {bbox_w:.6f} {bbox_h:.6f}")
if pan_count > 0:
valid_boxes.append(f"1 {x_center:.6f} {y_center:.6f} {bbox_w:.6f} {bbox_h:.6f}")
except Exception as e:
print(f"⚠️ Error processing polygon in {img_id}: {str(e)}")
continue
if valid_boxes:
with open(label_path, 'w') as f:
f.write("\n".join(valid_boxes))
shutil.copy(img_path, f"{CFG.work_dir}/{mode}/images/{img_id}.jpg")
processed_ids.append(img_id)
else:
missing_count += 1
print(f"⚠️ No valid boxes for {img_id} - Total entries: {len(group)}")
print(f"✓ Successfully processed {len(processed_ids)} images")
print(f"⚠️ Missing/Skipped {missing_count} images")
return processed_ids
My current solution achieved a competitive score but had room for improvement, particularly in handling images with numerous panels under single polygons. The main issue was that when a single polygon annotated many panels (e.g., 15+), the model might not detect each individual panel, leading to undercounting.
I have several ideas for improving the solution:
I'm proud of what I've accomplished with this solution. It demonstrates the effectiveness of YOLOv8 for detecting and counting solar installations in aerial imagery. While there's still work to be done, particularly in handling challenging polygon annotations, this solution has the potential to significantly impact renewable energy planning in Madagascar and similar regions by providing accurate, scalable detection of solar installations.
Thank you @zulo40 for sharing your insights. But Since time is running out, do you have a notebook readily available to share.
i have but i think i will share if after the competation ends .
Thank you. I'll be waiting
bruh