Spaces:
Sleeping
Sleeping
File size: 41,221 Bytes
f04d7cf |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 |
import cv2
import numpy as np
from matplotlib import pyplot as plt
class FruitDiseaseDetector:
def __init__(self):
self.disease_mask = None
self.healthy_mask = None
self.processed_image = None
def remove_background(self, image):
"""
Remove background from fruit image using multiple segmentation techniques
Returns the fruit mask and the image with background removed
"""
# Method 1: GrabCut algorithm (most effective for fruits)
fruit_mask_grabcut = self._grabcut_segmentation(image)
# Method 2: Color-based segmentation
fruit_mask_color = self._color_based_segmentation(image)
# Method 3: Edge-based segmentation
fruit_mask_edge = self._edge_based_segmentation(image)
# Combine all methods using voting
combined_mask = self._combine_masks([fruit_mask_grabcut, fruit_mask_color, fruit_mask_edge])
# Post-process the mask
final_mask = self._post_process_mask(combined_mask)
# Apply mask to image
result_image = image.copy()
result_image[final_mask == 0] = [0, 0, 0] # Set background to black
return final_mask, result_image
def _grabcut_segmentation(self, image):
"""Use GrabCut algorithm for mango foreground/background separation"""
height, width = image.shape[:2]
# Initialize mask
mask = np.zeros((height, width), np.uint8)
# Define rectangle around the mango (mangoes are typically oval/elongated)
# Adjust margins for mango shape - less margin on sides, more on top/bottom
margin_x = int(width * 0.10) # Reduced horizontal margin for mango width
margin_y = int(height * 0.12) # Slightly more vertical margin for mango length
rect = (margin_x, margin_y, width - 2*margin_x, height - 2*margin_y)
# Initialize background and foreground models
bgd_model = np.zeros((1, 65), np.float64)
fgd_model = np.zeros((1, 65), np.float64)
# Apply GrabCut with more iterations for better mango segmentation
cv2.grabCut(image, mask, rect, bgd_model, fgd_model, 8, cv2.GC_INIT_WITH_RECT)
# Create binary mask (0 for background, 1 for foreground)
fruit_mask = np.where((mask == 2) | (mask == 0), 0, 255).astype(np.uint8)
return fruit_mask
def _color_based_segmentation(self, image):
"""Segment mango using mango-specific color characteristics"""
# Convert to HSV for better color segmentation
hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
# Mango-specific color ranges (calibrated for mango dataset)
# Green mangoes (unripe) - broader green range for mangoes
lower_green1 = np.array([35, 25, 25])
upper_green1 = np.array([85, 255, 255])
# Yellow/Orange mangoes (ripe) - extended range for mango ripeness
lower_yellow = np.array([10, 30, 30])
upper_yellow = np.array([35, 255, 255])
# Red/Orange mangoes (very ripe) - specific to mango varieties
lower_red1 = np.array([0, 30, 30])
upper_red1 = np.array([15, 255, 255])
lower_red2 = np.array([165, 30, 30])
upper_red2 = np.array([180, 255, 255])
# Yellowish-green mangoes (semi-ripe)
lower_yellow_green = np.array([25, 20, 40])
upper_yellow_green = np.array([45, 255, 255])
# Create masks for mango color ranges
mask_green = cv2.inRange(hsv, lower_green1, upper_green1)
mask_yellow = cv2.inRange(hsv, lower_yellow, upper_yellow)
mask_red1 = cv2.inRange(hsv, lower_red1, upper_red1)
mask_red2 = cv2.inRange(hsv, lower_red2, upper_red2)
mask_yellow_green = cv2.inRange(hsv, lower_yellow_green, upper_yellow_green)
# Combine all mango color masks
fruit_mask = cv2.bitwise_or(mask_green, mask_yellow)
fruit_mask = cv2.bitwise_or(fruit_mask, mask_red1)
fruit_mask = cv2.bitwise_or(fruit_mask, mask_red2)
fruit_mask = cv2.bitwise_or(fruit_mask, mask_yellow_green)
# Include areas with moderate saturation and brightness (mango skin variations)
saturation = hsv[:, :, 1] # Saturation channel
value = hsv[:, :, 2] # Value channel
# Mango skin can have lower saturation but still be fruit
_, moderate_sat_mask = cv2.threshold(saturation, 30, 255, cv2.THRESH_BINARY)
_, bright_mask = cv2.threshold(value, 50, 255, cv2.THRESH_BINARY)
# Additional mask for pale/light mango areas
mango_skin_mask = cv2.bitwise_and(moderate_sat_mask, bright_mask)
# Final combination optimized for mangoes
fruit_mask = cv2.bitwise_or(fruit_mask, mango_skin_mask)
return fruit_mask
def _edge_based_segmentation(self, image):
"""Use edge detection and contour analysis for segmentation"""
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# Apply Gaussian blur
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
# Edge detection
edges = cv2.Canny(blurred, 50, 150)
# Find contours
contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# Create mask
mask = np.zeros(gray.shape, np.uint8)
if contours:
# Find the largest contour (assuming it's the fruit)
largest_contour = max(contours, key=cv2.contourArea)
# Only consider if the contour is reasonably large
if cv2.contourArea(largest_contour) > (gray.shape[0] * gray.shape[1] * 0.1):
cv2.fillPoly(mask, [largest_contour], 255)
return mask
def _combine_masks(self, masks):
"""Combine multiple masks using majority voting"""
height, width = masks[0].shape
combined = np.zeros((height, width), dtype=np.uint8)
# Convert masks to binary (0 or 1)
binary_masks = [(mask > 127).astype(np.uint8) for mask in masks]
# Sum all masks
mask_sum = np.sum(binary_masks, axis=0)
# Use majority voting (at least 2 out of 3 methods agree)
combined[mask_sum >= 2] = 255
return combined
def _post_process_mask(self, mask):
"""Clean up the mask using morphological operations"""
# Remove small noise
kernel_small = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
cleaned = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel_small)
# Fill small holes
kernel_large = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (15, 15))
cleaned = cv2.morphologyEx(cleaned, cv2.MORPH_CLOSE, kernel_large)
# Find the largest connected component (should be the main fruit)
num_labels, labels, stats, _ = cv2.connectedComponentsWithStats(cleaned, connectivity=8)
if num_labels > 1:
# Find largest component (excluding background)
largest_label = 1 + np.argmax(stats[1:, cv2.CC_STAT_AREA])
# Create mask with only the largest component
final_mask = np.zeros_like(cleaned)
final_mask[labels == largest_label] = 255
else:
final_mask = cleaned
return final_mask
def preprocess_image(self, image):
"""Preprocess the input image for disease detection"""
# Convert to different color spaces for better analysis
hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
lab = cv2.cvtColor(image, cv2.COLOR_BGR2LAB)
# Apply Gaussian blur to reduce noise
blurred = cv2.GaussianBlur(image, (5, 5), 0)
return blurred, hsv, lab
def detect_diseased_areas(self, image, fruit_mask=None):
"""
Detect mango diseases using color-based segmentation and texture analysis
Calibrated for: Alternaria, Anthracnose, Aspergillus (Black Mould), Lasiodiplodia (Stem Rot)
"""
blurred, hsv, lab = self.preprocess_image(image)
# Method 1: Detect Anthracnose (dark circular spots with orange/pink halos)
disease_mask1 = self._detect_anthracnose(hsv, lab)
# Method 2: Detect Alternaria (brown/black irregular spots)
disease_mask2 = self._detect_alternaria(hsv, lab)
# Method 3: Detect Aspergillus (Black Mould Rot - dark, fuzzy patches)
disease_mask3 = self._detect_aspergillus(hsv, lab)
# Method 4: Detect Lasiodiplodia (Stem and Rot - soft, dark areas)
disease_mask4 = self._detect_lasiodiplodia(hsv, lab)
# Method 5: General texture-based detection for rough/irregular surfaces
disease_mask5 = self._detect_texture_anomalies(blurred)
# Method 6: Edge-based detection for irregular boundaries
disease_mask6 = self._detect_irregular_edges(blurred)
# Combine disease-specific methods first (higher confidence)
primary_disease_mask = cv2.bitwise_or(disease_mask1, disease_mask2)
primary_disease_mask = cv2.bitwise_or(primary_disease_mask, disease_mask3)
primary_disease_mask = cv2.bitwise_or(primary_disease_mask, disease_mask4)
# Secondary detection (texture and edges) - use only if there's significant evidence
secondary_mask = cv2.bitwise_and(disease_mask5, disease_mask6)
# Combine primary and secondary with different weights
combined_mask = cv2.bitwise_or(primary_disease_mask, secondary_mask)
# Apply fruit mask to limit detection to mango area only
if fruit_mask is not None:
combined_mask = cv2.bitwise_and(combined_mask, fruit_mask)
# Post-processing: More aggressive noise removal for better accuracy
kernel_small = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
kernel_medium = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
# Remove small noise (more aggressive)
combined_mask = cv2.morphologyEx(combined_mask, cv2.MORPH_OPEN, kernel_small)
combined_mask = cv2.morphologyEx(combined_mask, cv2.MORPH_OPEN, kernel_medium)
# Fill small holes but not too aggressively
kernel_close = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
combined_mask = cv2.morphologyEx(combined_mask, cv2.MORPH_CLOSE, kernel_close)
# Filter out very small regions (likely noise)
contours, _ = cv2.findContours(combined_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
filtered_mask = np.zeros_like(combined_mask)
min_area = 75 # Balanced minimum area for good detection
for contour in contours:
if cv2.contourArea(contour) >= min_area:
cv2.fillPoly(filtered_mask, [contour], 255)
return filtered_mask
def _detect_anthracnose(self, hsv, lab):
"""Detect Anthracnose disease - dark circular spots with orange/pink halos"""
# Anthracnose characteristics: Dark centers with lighter halos
# Use both HSV and LAB for better detection
# Dark spots in HSV (less conservative thresholds)
lower_dark = np.array([0, 40, 0])
upper_dark = np.array([180, 255, 80])
dark_mask = cv2.inRange(hsv, lower_dark, upper_dark)
# Reddish-brown areas (anthracnose lesions) - broader range
lower_reddish = np.array([0, 50, 15])
upper_reddish = np.array([25, 255, 140])
reddish_mask = cv2.inRange(hsv, lower_reddish, upper_reddish)
# LAB color space for better brown/dark detection
l_channel = lab[:, :, 0]
a_channel = lab[:, :, 1]
# Less conservative dark areas with reddish tint
_, dark_lab = cv2.threshold(l_channel, 85, 255, cv2.THRESH_BINARY_INV)
_, red_lab = cv2.threshold(a_channel, 135, 255, cv2.THRESH_BINARY)
lab_anthracnose = cv2.bitwise_and(dark_lab, red_lab)
# Combine HSV and LAB detections - use OR instead of AND for better sensitivity
anthracnose_mask = cv2.bitwise_or(dark_mask, reddish_mask)
anthracnose_mask = cv2.bitwise_or(anthracnose_mask, lab_anthracnose)
return anthracnose_mask
def _detect_alternaria(self, hsv, lab):
"""Detect Alternaria disease - brown/black irregular spots with concentric rings"""
# Alternaria has characteristic brown to black lesions
# Less conservative brown to black spots in HSV
lower_brown1 = np.array([5, 60, 5])
upper_brown1 = np.array([25, 255, 100])
lower_brown2 = np.array([0, 40, 3])
upper_brown2 = np.array([20, 255, 80])
brown_mask1 = cv2.inRange(hsv, lower_brown1, upper_brown1)
brown_mask2 = cv2.inRange(hsv, lower_brown2, upper_brown2)
# Very dark areas (advanced alternaria) - less conservative
lower_black = np.array([0, 30, 0])
upper_black = np.array([180, 255, 50])
black_mask = cv2.inRange(hsv, lower_black, upper_black)
# LAB space detection for brown lesions - less strict
l_channel = lab[:, :, 0]
_, dark_lab = cv2.threshold(l_channel, 70, 255, cv2.THRESH_BINARY_INV)
# Combine alternaria indicators - use OR for better sensitivity
alternaria_mask = cv2.bitwise_or(brown_mask1, brown_mask2)
alternaria_mask = cv2.bitwise_or(alternaria_mask, black_mask)
alternaria_mask = cv2.bitwise_or(alternaria_mask, dark_lab)
return alternaria_mask
def _detect_aspergillus(self, hsv, lab):
"""Detect Aspergillus (Black Mould Rot) - dark, fuzzy patches with irregular borders"""
# Aspergillus appears as very dark, irregular patches with possible greenish tints
# Very dark areas in HSV - less conservative
lower_black = np.array([0, 20, 0])
upper_black = np.array([180, 255, 40])
black_mask = cv2.inRange(hsv, lower_black, upper_black)
# Dark greenish areas (aspergillus can have greenish tint) - broader range
lower_dark_green = np.array([40, 40, 3])
upper_dark_green = np.array([80, 255, 60])
dark_green_mask = cv2.inRange(hsv, lower_dark_green, upper_dark_green)
# Dark bluish-green areas (specific to aspergillus) - broader range
lower_blue_green = np.array([85, 30, 5])
upper_blue_green = np.array([120, 255, 80])
blue_green_mask = cv2.inRange(hsv, lower_blue_green, upper_blue_green)
# LAB space for very dark areas - less conservative
l_channel = lab[:, :, 0]
b_channel = lab[:, :, 2]
_, very_dark_lab = cv2.threshold(l_channel, 50, 255, cv2.THRESH_BINARY_INV)
# Blue-green tint in LAB space (aspergillus characteristic) - broader range
_, blue_tint = cv2.threshold(b_channel, 110, 255, cv2.THRESH_BINARY_INV)
lab_aspergillus = cv2.bitwise_and(very_dark_lab, blue_tint)
# Combine aspergillus indicators - use OR for better sensitivity
aspergillus_mask = cv2.bitwise_or(black_mask, very_dark_lab)
aspergillus_mask = cv2.bitwise_or(aspergillus_mask, dark_green_mask)
aspergillus_mask = cv2.bitwise_or(aspergillus_mask, blue_green_mask)
aspergillus_mask = cv2.bitwise_or(aspergillus_mask, lab_aspergillus)
return aspergillus_mask
def _detect_lasiodiplodia(self, hsv, lab):
"""Detect Lasiodiplodia (Stem and Rot disease) - soft, dark areas with brown/black coloration"""
# Lasiodiplodia characteristics: dark, soft areas with brown/black coloration
# Brown rot areas - less conservative
lower_rot = np.array([8, 50, 10])
upper_rot = np.array([30, 255, 120])
rot_mask = cv2.inRange(hsv, lower_rot, upper_rot)
# Dark spots with higher saturation (active rot) - less strict
lower_active_rot = np.array([0, 60, 5])
upper_active_rot = np.array([20, 255, 100])
active_rot_mask = cv2.inRange(hsv, lower_active_rot, upper_active_rot)
# Very dark areas (advanced lasiodiplodia) - broader range
lower_very_dark = np.array([0, 30, 0])
upper_very_dark = np.array([30, 255, 60])
very_dark_mask = cv2.inRange(hsv, lower_very_dark, upper_very_dark)
# LAB space for detecting rot areas - less conservative
l_channel = lab[:, :, 0]
a_channel = lab[:, :, 1]
_, dark_rot = cv2.threshold(l_channel, 70, 255, cv2.THRESH_BINARY_INV)
_, red_tint = cv2.threshold(a_channel, 130, 255, cv2.THRESH_BINARY)
lab_rot = cv2.bitwise_and(dark_rot, red_tint)
# Combine lasiodiplodia indicators - use OR for better sensitivity
lasiodiplodia_mask = cv2.bitwise_or(rot_mask, active_rot_mask)
lasiodiplodia_mask = cv2.bitwise_or(lasiodiplodia_mask, very_dark_mask)
lasiodiplodia_mask = cv2.bitwise_or(lasiodiplodia_mask, lab_rot)
return lasiodiplodia_mask
def _detect_black_mould_rot(self, hsv, lab):
"""Detect Black Mould Rot - dark, fuzzy patches with irregular borders (legacy method)"""
# This method is now replaced by _detect_aspergillus but kept for compatibility
return self._detect_aspergillus(hsv, lab)
def _detect_stem_rot(self, hsv, lab):
"""Detect Stem and Rot disease - soft, dark areas typically near stem end (legacy method)"""
# This method is now replaced by _detect_lasiodiplodia but kept for compatibility
return self._detect_lasiodiplodia(hsv, lab)
def _detect_brown_spots(self, hsv):
"""Detect brown/dark spots typical of fruit diseases (legacy method)"""
# Define HSV range for brown/dark diseased areas
# Brown spots: Low saturation, low value, wide hue range
lower_brown = np.array([0, 20, 20])
upper_brown = np.array([30, 255, 120])
# Create mask for brown areas
brown_mask1 = cv2.inRange(hsv, lower_brown, upper_brown)
# Additional brown range (reddish-brown)
lower_brown2 = np.array([150, 20, 20])
upper_brown2 = np.array([180, 255, 120])
brown_mask2 = cv2.inRange(hsv, lower_brown2, upper_brown2)
# Combine brown masks
brown_mask = cv2.bitwise_or(brown_mask1, brown_mask2)
return brown_mask
def _detect_dark_spots_lab(self, lab):
"""Detect dark spots using LAB color space"""
l_channel = lab[:, :, 0]
a_channel = lab[:, :, 1]
# Dark areas have low L values
_, dark_mask = cv2.threshold(l_channel, 80, 255, cv2.THRESH_BINARY_INV)
# Areas with high 'a' values (reddish) combined with darkness
_, red_mask = cv2.threshold(a_channel, 140, 255, cv2.THRESH_BINARY)
# Combine dark and reddish areas
lab_mask = cv2.bitwise_and(dark_mask, red_mask)
return lab_mask
def _detect_texture_anomalies(self, image):
"""Detect texture anomalies using local binary patterns concept (calibrated for mango)"""
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# Calculate local standard deviation (texture measure) - optimized for mango texture
kernel = np.ones((7, 7), np.float32) / 49 # Slightly smaller kernel for mango details
gray_float = gray.astype(np.float32)
mean = cv2.filter2D(gray_float, -1, kernel)
sqr_mean = cv2.filter2D(gray_float**2, -1, kernel)
# Ensure variance is non-negative (handle floating point precision errors)
variance = sqr_mean - mean**2
variance = np.maximum(variance, 0) # Clamp negative values to 0
std_dev = np.sqrt(variance)
# Handle NaN and infinity values
std_dev = np.nan_to_num(std_dev, nan=0.0, posinf=255.0, neginf=0.0)
# Normalize to 0-255 range
if std_dev.max() > 0:
std_dev = (std_dev / std_dev.max() * 255).astype(np.uint8)
else:
std_dev = std_dev.astype(np.uint8)
# Higher threshold for mango diseases (less sensitive to normal texture)
_, texture_mask = cv2.threshold(std_dev, 30, 255, cv2.THRESH_BINARY)
# Additional texture analysis for mango-specific roughness
# Use Sobel operators to detect rough areas - more conservative
sobelx = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=3)
sobely = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=3)
sobel_magnitude = np.sqrt(sobelx**2 + sobely**2)
sobel_magnitude = np.uint8(sobel_magnitude / sobel_magnitude.max() * 255)
# Higher threshold for rough texture detection (less sensitive)
_, rough_mask = cv2.threshold(sobel_magnitude, 50, 255, cv2.THRESH_BINARY)
# Combine standard deviation and sobel-based texture detection
# Use AND operation to require both methods to agree (more conservative)
final_texture_mask = cv2.bitwise_and(texture_mask, rough_mask)
return final_texture_mask
def _detect_irregular_edges(self, image):
"""Detect irregular edges that might indicate disease boundaries"""
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# Apply Canny edge detection
edges = cv2.Canny(gray, 50, 150)
# Dilate edges to create regions
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
dilated_edges = cv2.dilate(edges, kernel, iterations=2)
return dilated_edges
def calculate_disease_severity(self, image, disease_mask, fruit_mask):
"""Calculate disease severity as percentage of affected area"""
if fruit_mask is not None:
# Use provided fruit mask
fruit_area = cv2.countNonZero(fruit_mask)
else:
# Fallback: create fruit mask (assuming fruit takes up most of the image)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
_, fruit_mask = cv2.threshold(gray, 30, 255, cv2.THRESH_BINARY)
fruit_area = cv2.countNonZero(fruit_mask)
# Calculate disease area (only within fruit region)
if fruit_mask is not None:
disease_in_fruit = cv2.bitwise_and(disease_mask, fruit_mask)
disease_area = cv2.countNonZero(disease_in_fruit)
else:
disease_area = cv2.countNonZero(disease_mask)
if fruit_area == 0:
return 0
severity_percentage = (disease_area / fruit_area) * 100
return min(severity_percentage, 100) # Cap at 100%
def classify_disease_level(self, severity_percentage):
"""Classify mango disease level based on severity percentage (calibrated for mango)"""
if severity_percentage < 2:
return "Healthy", (0, 255, 0) # Green - very strict for healthy
elif severity_percentage < 8:
return "Early Disease", (0, 255, 255) # Yellow - early detection
elif severity_percentage < 20:
return "Moderate Disease", (0, 165, 255) # Orange - moderate infection
elif severity_percentage < 40:
return "Severe Disease", (0, 100, 255) # Red-Orange - severe infection
else:
return "Critical Disease", (0, 0, 255) # Red - critical, unmarketable
def process_image(self, image_path):
"""Main processing function with background removal (calibrated for mango diseases)"""
# Load image
image = cv2.imread(image_path)
if image is None:
raise ValueError("Could not load image")
print("Step 1: Removing background (mango-optimized)...")
# Remove background first
fruit_mask, image_no_bg = self.remove_background(image)
print("Step 2: Detecting mango diseases (Alternaria, Anthracnose, Aspergillus, Lasiodiplodia)...")
# Detect diseased areas (only within mango region)
disease_mask = self.detect_diseased_areas(image_no_bg, fruit_mask)
print("Step 3: Calculating mango disease severity...")
# Calculate severity
severity = self.calculate_disease_severity(image_no_bg, disease_mask, fruit_mask)
disease_level, color = self.classify_disease_level(severity)
print("Step 4: Creating mango disease visualization...")
# Create output visualization with bounding boxes
output_image, disease_info = self._create_output_visualization(image, disease_mask, severity, disease_level, color, fruit_mask)
# Store results
self.disease_mask = disease_mask
self.processed_image = output_image
self.fruit_mask = fruit_mask
self.image_no_bg = image_no_bg
self.disease_info = disease_info
return {
'severity_percentage': severity,
'disease_level': disease_level,
'disease_mask': disease_mask,
'output_image': output_image,
'fruit_mask': fruit_mask,
'image_no_bg': image_no_bg,
'disease_info': disease_info,
'num_diseased_regions': len(disease_info)
}
def _create_output_visualization(self, original, mask, severity, level, color, fruit_mask=None):
"""Create visualization showing detected diseased areas with bounding boxes"""
# Start with original image
result = original.copy()
# If we have a fruit mask, dim the background
if fruit_mask is not None:
background = np.zeros_like(original)
result = np.where(fruit_mask[..., np.newaxis] == 0,
background, result)
# Create colored overlay for diseased areas
overlay = result.copy()
disease_info = []
if np.any(mask > 0): # Only if there are diseased areas
overlay[mask > 0] = color
# Blend original image with overlay
result = cv2.addWeighted(result, 0.7, overlay, 0.3, 0)
# Find contours and draw bounding boxes
contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# Filter contours by minimum area to avoid tiny noise
min_area = 50 # Minimum area threshold
valid_contours = [cnt for cnt in contours if cv2.contourArea(cnt) >= min_area]
# Draw contours and bounding boxes
for i, contour in enumerate(valid_contours):
# Draw contour outline
cv2.drawContours(result, [contour], -1, color, 2)
# Get bounding rectangle
x, y, w, h = cv2.boundingRect(contour)
# Draw bounding box
cv2.rectangle(result, (x, y), (x + w, y + h), color, 3)
# Calculate area of this diseased region
area = cv2.contourArea(contour)
disease_info.append({
'id': i + 1,
'bbox': (x, y, w, h),
'area': area,
'center': (x + w//2, y + h//2)
})
# Add label with disease ID
label = f"D{i+1}"
label_size = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.6, 2)[0]
# Position label above bounding box
label_x = x
label_y = y - 10 if y - 10 > 20 else y + h + 25
# Draw label background
cv2.rectangle(result,
(label_x - 2, label_y - label_size[1] - 2),
(label_x + label_size[0] + 2, label_y + 2),
color, -1)
# Draw label text
cv2.putText(result, label, (label_x, label_y),
cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 2)
# Add overall statistics
num_diseases = len(disease_info)
text1 = f"{level}: {severity:.1f}%"
text2 = f"Diseased Regions: {num_diseases}"
# Main status text
cv2.putText(result, text1, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, color, 2)
cv2.putText(result, text2, (10, 70), cv2.FONT_HERSHEY_SIMPLEX, 0.8, color, 2)
# Add fruit outline
if fruit_mask is not None:
fruit_contours, _ = cv2.findContours(fruit_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(result, fruit_contours, -1, (255, 255, 255), 2)
return result, disease_info
def save_detailed_report(self, output_path, results):
"""Save detailed report of disease detection results"""
report_path = output_path.replace('.jpg', '_report.txt').replace('.png', '_report.txt')
with open(report_path, 'w') as f:
f.write("=== FRUIT DISEASE DETECTION REPORT ===\n")
f.write(f"Image processed: {output_path}\n")
f.write(f"Overall Disease Level: {results['disease_level']}\n")
f.write(f"Overall Severity: {results['severity_percentage']:.2f}%\n")
f.write(f"Number of Diseased Regions: {results['num_diseased_regions']}\n")
f.write("\n=== INDIVIDUAL DISEASE REGIONS ===\n")
if results['disease_info']:
for disease in results['disease_info']:
f.write(f"\nDisease Region D{disease['id']}:\n")
f.write(f" - Bounding Box: x={disease['bbox'][0]}, y={disease['bbox'][1]}, ")
f.write(f"width={disease['bbox'][2]}, height={disease['bbox'][3]}\n")
f.write(f" - Area: {disease['area']:.0f} pixels\n")
f.write(f" - Center: ({disease['center'][0]}, {disease['center'][1]})\n")
else:
f.write("No diseased regions detected.\n")
print(f"Detailed report saved: {report_path}")
return report_path
def save_results(self, output_path, include_mask=True, include_background_removed=True):
"""Save processing results"""
if self.processed_image is not None:
cv2.imwrite(output_path, self.processed_image)
print(f"Main result saved: {output_path}")
if include_mask and self.disease_mask is not None:
mask_path = output_path.replace('.', '_disease_mask.')
cv2.imwrite(mask_path, self.disease_mask)
print(f"Disease mask saved: {mask_path}")
if include_background_removed and hasattr(self, 'image_no_bg') and self.image_no_bg is not None:
bg_removed_path = output_path.replace('.', '_no_background.')
cv2.imwrite(bg_removed_path, self.image_no_bg)
print(f"Background removed image saved: {bg_removed_path}")
if hasattr(self, 'fruit_mask') and self.fruit_mask is not None:
fruit_mask_path = output_path.replace('.', '_fruit_mask.')
cv2.imwrite(fruit_mask_path, self.fruit_mask)
print(f"Fruit mask saved: {fruit_mask_path}")
# Example usage and testing function
def demonstrate_disease_detection():
"""Demonstrate the mango disease detection algorithm"""
detector = FruitDiseaseDetector()
print("Mango Disease Detection Algorithm with Background Removal & Bounding Boxes")
print("========================================================================")
print("This algorithm detects mango diseases using:")
print("1. Mango-optimized background removal (GrabCut + Color + Edge detection)")
print("2. Anthracnose detection (dark circular spots with orange/pink halos)")
print("3. Alternaria detection (brown/black irregular spots with concentric rings)")
print("4. Aspergillus detection (black mould with greenish tints)")
print("5. Lasiodiplodia detection (stem rot with brown/black soft areas)")
print("6. Mango-calibrated texture analysis for rough surfaces")
print("7. Edge detection for irregular disease boundaries")
print("8. Individual disease region bounding boxes")
print()
print("Algorithm Features for Mango:")
print("- Multi-method background removal optimized for mango shape")
print("- Disease-specific detection for common mango diseases")
print("- Individual region labeling (D1, D2, D3, etc.)")
print("- Mango-specific severity assessment (Healthy < 2%, Early < 8%, etc.)")
print("- Comprehensive reporting for mango disease management")
print("- Calibrated for mango color variations (green, yellow, orange, red)")
print("- Specialized detection for Aspergillus (Black Mould) and Lasiodiplodia (Stem Rot)")
return detector
# Batch testing function for algorithm validation
def test_mango_detection_algorithm():
"""Test the algorithm on multiple mango samples"""
detector = FruitDiseaseDetector()
# Test cases with expected results - using correct file names
test_cases = [
("SenMangoFruitDDS_bgremoved/Healthy/healthy_003.jpg", "Should be Healthy"),
("SenMangoFruitDDS_bgremoved/Healthy/healthy_010.jpg", "Should be Healthy"),
("SenMangoFruitDDS_bgremoved/Alternaria/Alternaria_005.jpg", "Should detect Alternaria"),
("SenMangoFruitDDS_bgremoved/Alternaria/Alternaria_010.jpg", "Should detect Alternaria"),
("SenMangoFruitDDS_bgremoved/Anthracnose/Anthracnose_002.jpg", "Should detect Anthracnose"),
("SenMangoFruitDDS_bgremoved/Anthracnose/Anthracnose_010.jpg", "Should detect Anthracnose"),
("SenMangoFruitDDS_bgremoved/Black Mould Rot/Aspergillus_001.jpg", "Should detect Aspergillus (Black Mould)"),
("SenMangoFruitDDS_bgremoved/Black Mould Rot/Aspergillus_010.jpg", "Should detect Aspergillus (Black Mould)"),
("SenMangoFruitDDS_bgremoved/Stem and Rot/Lasiodiplodia_001.jpg", "Should detect Lasiodiplodia (Stem Rot)"),
("SenMangoFruitDDS_bgremoved/Stem and Rot/Lasiodiplodia_012.jpg", "Should detect Lasiodiplodia (Stem Rot)"),
]
print("=== MANGO DISEASE DETECTION ALGORITHM VALIDATION ===")
print("Testing multiple samples to validate algorithm performance")
print("Diseases: Alternaria, Anthracnose, Aspergillus (Black Mould), Lasiodiplodia (Stem Rot)")
print("=" * 80)
results_summary = []
for i, (image_path, expected) in enumerate(test_cases, 1):
print(f"\nTest {i}: {image_path}")
print(f"Expected: {expected}")
try:
results = detector.process_image(image_path)
output_path = f"test_result_{i}.jpg"
result_info = {
'test_id': i,
'image_path': image_path,
'expected': expected,
'detected_level': results['disease_level'],
'severity': results['severity_percentage'],
'num_regions': results['num_diseased_regions']
}
results_summary.append(result_info)
print(f"Result: {results['disease_level']} ({results['severity_percentage']:.2f}%)")
print(f"Regions: {results['num_diseased_regions']}")
# Save test result
detector.save_results(output_path, include_mask=True)
except Exception as e:
print(f"Error: {e}")
results_summary.append({
'test_id': i,
'image_path': image_path,
'expected': expected,
'detected_level': 'ERROR',
'severity': 0.0,
'num_regions': 0
})
print("-" * 50)
# Print summary
print("\n" + "=" * 80)
print("TESTING SUMMARY")
print("=" * 80)
for result in results_summary:
status = "β" if result['detected_level'] != 'Healthy' and 'Should detect' in result['expected'] else "β" if result['detected_level'] == 'Healthy' and 'Should detect' in result['expected'] else "β"
print(f"Test {result['test_id']:2d}: {status} {result['detected_level']} ({result['severity']:.1f}%) - {result['expected']}")
return results_summary
# Usage example:
if __name__ == "__main__":
# Initialize mango disease detector
detector = FruitDiseaseDetector()
# Test different disease types - uncomment to test specific diseases:
# image_path = "SenMangoFruitDDS_bgremoved/Healthy/healthy_003.jpg" # Should be Healthy
# image_path = "SenMangoFruitDDS_bgremoved/Alternaria/Alternaria_005.jpg" # Should detect Alternaria
# image_path = "SenMangoFruitDDS_bgremoved/Anthracnose/Anthracnose_002.jpg" # Should detect Anthracnose
# image_path = "SenMangoFruitDDS_bgremoved/Black Mould Rot/Aspergillus_001.jpg" # Should detect Aspergillus
image_path = "SenMangoFruitDDS_bgremoved/Stem and Rot/Lasiodiplodia_012.jpg" # Should detect Lasiodiplodia
output_path = "mango_disease_detection_result.jpg" # Output file name
try:
print("Processing mango image with calibrated algorithm...")
print(f"Input: {image_path}")
print(f"Output: {output_path}")
print("-" * 60)
results = detector.process_image(image_path)
print(f"\n=== MANGO DISEASE DETECTION RESULTS ===")
print(f"Disease Level: {results['disease_level']}")
print(f"Severity: {results['severity_percentage']:.2f}%")
print(f"Number of Diseased Regions: {results['num_diseased_regions']}")
# Print individual disease information
if results['disease_info']:
print(f"\n=== INDIVIDUAL DISEASE REGIONS ===")
for disease in results['disease_info']:
print(f"Disease D{disease['id']}: Area={disease['area']:.0f} pixels, "
f"Center=({disease['center'][0]}, {disease['center'][1]})")
else:
print("\n=== MANGO HEALTH STATUS ===")
print("No significant disease regions detected - mango appears healthy!")
# Save results
detector.save_results(output_path, include_mask=True)
# Save detailed report
detector.save_detailed_report(output_path, results)
print(f"\n=== FILES SAVED ===")
print(f"Main result: {output_path}")
print(f"Disease mask: {output_path.replace('.', '_disease_mask.')}")
print(f"Background removed: {output_path.replace('.', '_no_background.')}")
print(f"Fruit mask: {output_path.replace('.', '_fruit_mask.')}")
print(f"Detailed report: {output_path.replace('.jpg', '_report.txt')}")
print("\nMango disease detection completed successfully!")
print("The algorithm has been calibrated for mango-specific diseases:")
print("- Alternaria, Anthracnose, Aspergillus (Black Mould), Lasiodiplodia (Stem Rot)")
except FileNotFoundError:
print(f"Error: Could not find mango image file '{image_path}'")
print("Please check the file path and make sure the image exists.")
print("\nAvailable test images:")
print("- SenMangoFruitDDS_bgremoved/Healthy/healthy_003.jpg")
print("- SenMangoFruitDDS_bgremoved/Alternaria/Alternaria_005.jpg")
print("- SenMangoFruitDDS_bgremoved/Anthracnose/Anthracnose_002.jpg")
except Exception as e:
print(f"Error processing mango image: {e}")
# Uncomment below to see algorithm demonstration
# demonstrate_disease_detection()
# Uncomment below to run batch testing
# test_mango_detection_algorithm() |