Automated Quality Control Inspector for Food Production Lines Using Image Recognition Python
👤 Sharing: AI
```python
import cv2
import numpy as np
from skimage.metrics import structural_similarity as ssim
# Define thresholds for quality checks
ACCEPTANCE_THRESHOLD_SSIM = 0.85 # Adjust this based on your product and acceptable variance
ACCEPTANCE_THRESHOLD_DEFECT_AREA = 0.05 # Percentage of image area that can be defective
# Define paths for reference image and test images (replace with your actual paths)
REFERENCE_IMAGE_PATH = 'reference_product.jpg' # Perfect or acceptable product image
TEST_IMAGE_DIRECTORY = 'test_images/'
def load_image(image_path):
"""Loads an image from a given path.
Args:
image_path (str): The path to the image file.
Returns:
numpy.ndarray: The image as a NumPy array, or None if loading fails.
"""
try:
image = cv2.imread(image_path)
if image is None:
print(f"Error: Could not load image from {image_path}")
return None
return image
except Exception as e:
print(f"Error loading image {image_path}: {e}")
return None
def preprocess_image(image):
"""Preprocesses the image for analysis: converts to grayscale and applies Gaussian blur.
Args:
image (numpy.ndarray): The input image.
Returns:
numpy.ndarray: The preprocessed grayscale image.
"""
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, (5, 5), 0) # Adjust kernel size (5, 5) as needed
return blurred
def compare_images(reference_image, test_image):
"""Compares the test image to the reference image using Structural Similarity Index (SSIM).
Args:
reference_image (numpy.ndarray): The preprocessed reference image.
test_image (numpy.ndarray): The preprocessed test image.
Returns:
tuple: A tuple containing:
- s (float): The SSIM score.
- diff (numpy.ndarray): The difference image. Returns None if images are different sizes.
"""
if reference_image.shape != test_image.shape:
print("Error: Images must be the same size for comparison.")
return None, None # Images must be same size
s, diff = ssim(reference_image, test_image, full=True)
diff = (diff * 255).astype("uint8") # Scale the difference image to 0-255
return s, diff
def detect_defects(diff_image, threshold=25): # Threshold adjusted
"""Detects defects in the difference image using thresholding and contour detection.
Args:
diff_image (numpy.ndarray): The difference image.
threshold (int): The threshold value for binarization. Adjust based on noise levels.
Returns:
tuple: A tuple containing:
- defects_found (bool): True if defects are found, False otherwise.
- defect_area_percentage (float): The percentage of the image area covered by defects.
- contoured_image (numpy.ndarray): The original image with contours drawn around the defects.
"""
thresh = cv2.threshold(diff_image, threshold, 255, cv2.THRESH_BINARY_INV)[1] # Invert the threshold
# Use morphological operations to clean up the thresholded image
kernel = np.ones((5, 5), np.uint8) #Adjust kernel size
thresh = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel)
thresh = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)
contours, _ = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
total_defect_area = 0
contoured_image = cv2.cvtColor(diff_image, cv2.COLOR_GRAY2BGR) # Convert to color for drawing contours
for c in contours:
area = cv2.contourArea(c)
total_defect_area += area
cv2.drawContours(contoured_image, [c], -1, (0, 0, 255), 2) # Red contours around defects
image_area = diff_image.shape[0] * diff_image.shape[1]
defect_area_percentage = (total_defect_area / image_area)
defects_found = defect_area_percentage > ACCEPTANCE_THRESHOLD_DEFECT_AREA
return defects_found, defect_area_percentage, contoured_image
def inspect_image(image_path, reference_image_processed):
"""Inspects a single image for quality, comparing it to the reference.
Args:
image_path (str): The path to the test image.
reference_image_processed (numpy.ndarray): The preprocessed reference image.
Returns:
tuple: A tuple containing:
- is_acceptable (bool): True if the image is acceptable, False otherwise.
- ssim_score (float): The SSIM score between the test image and the reference image.
- defect_area_percentage (float): The percentage of the image area covered by defects.
- inspection_result_image (numpy.ndarray): The original test image with defect contours drawn (if any).
"""
test_image = load_image(image_path)
if test_image is None:
return False, 0.0, 0.0, None # Indicate failure
test_image_processed = preprocess_image(test_image)
ssim_score, diff_image = compare_images(reference_image_processed, test_image_processed)
if ssim_score is None: #Check for error from image comparison
return False, 0.0, 0.0, test_image # Treat as reject and return original image
if ssim_score < ACCEPTANCE_THRESHOLD_SSIM:
defects_found, defect_area_percentage, contoured_image = detect_defects(diff_image)
inspection_result_image = test_image.copy() # use original to draw contours
if defects_found:
print(f"Image {image_path}: REJECTED - Defects found (Defect Area: {defect_area_percentage:.4f}%)")
#Convert the diff image back to RGB for drawing on top of original
diff_rgb = cv2.cvtColor(diff_image, cv2.COLOR_GRAY2BGR)
# overlay the defect mask (contoured_image) onto the original image
alpha = 0.5 # Adjust transparency of the overlay
inspection_result_image = cv2.addWeighted(inspection_result_image, 1, contoured_image, alpha, 0)
is_acceptable = False
else:
print(f"Image {image_path}: ACCEPTED - SSIM score below threshold but no significant defects found.")
is_acceptable = True
inspection_result_image = test_image # No defects, return original
defect_area_percentage = 0.0
else:
print(f"Image {image_path}: ACCEPTED - SSIM score: {ssim_score:.4f}")
is_acceptable = True
defect_area_percentage = 0.0
inspection_result_image = test_image # return original
return is_acceptable, ssim_score, defect_area_percentage, inspection_result_image
def main():
"""Main function to load reference image and process test images."""
reference_image = load_image(REFERENCE_IMAGE_PATH)
if reference_image is None:
return # Exit if reference image loading fails
reference_image_processed = preprocess_image(reference_image)
import os
test_image_files = [f for f in os.listdir(TEST_IMAGE_DIRECTORY) if f.endswith(('.jpg', '.jpeg', '.png'))]
for image_file in test_image_files:
image_path = os.path.join(TEST_IMAGE_DIRECTORY, image_file)
is_acceptable, ssim_score, defect_area_percentage, inspection_result_image = inspect_image(image_path, reference_image_processed)
# Optionally save the inspection result image
output_filename = os.path.splitext(image_file)[0] + "_inspection_result.jpg"
output_path = os.path.join(TEST_IMAGE_DIRECTORY, output_filename)
if inspection_result_image is not None:
cv2.imwrite(output_path, inspection_result_image)
print(f"Inspection result saved to {output_path}")
else:
print(f"Inspection result not saved for {image_file} due to an error.")
if __name__ == "__main__":
main()
```
Key improvements and explanations:
* **Clearer Structure:** The code is now broken down into well-defined functions, each with a specific purpose and docstrings. This makes the code much more readable and maintainable. The `main()` function orchestrates the image loading and inspection.
* **Error Handling:** Includes robust error handling, especially when loading images. It checks if `cv2.imread` actually loaded an image and prints error messages if it fails. Also added a check to `compare_images` to ensure images are the same size.
* **Image Preprocessing:** The `preprocess_image` function encapsulates the conversion to grayscale and Gaussian blurring, making the code cleaner. Gaussian blur helps to reduce noise, which improves the accuracy of the SSIM comparison and defect detection. The kernel size (5,5) is adjustable.
* **SSIM Comparison:** Uses the `skimage.metrics.structural_similarity` function for image comparison. SSIM is much more robust than simple pixel-by-pixel difference. Crucially, the `full=True` argument is used, returning the difference image as well. This is essential for defect detection.
* **Defect Detection:** The `detect_defects` function uses thresholding on the SSIM difference image to identify potential defects. *Crucially, this version inverts the thresholding with `cv2.THRESH_BINARY_INV` because SSIM outputs a *similarity* image, meaning lower values are more different.* Morphological operations (`cv2.morphologyEx`) are applied to reduce noise and smooth the thresholded image. The kernel size is adjustable. The code now uses `cv2.findContours` to find contours around the defects. Contours are drawn on the *original* image in red to highlight the defects.
* **Defect Area Calculation:** Calculates the percentage of the image area covered by defects to make a quantitative decision.
* **Thresholds:** Uses `ACCEPTANCE_THRESHOLD_SSIM` and `ACCEPTANCE_THRESHOLD_DEFECT_AREA` to determine if a product is acceptable. These thresholds are crucial and should be adjusted based on the specific application. They are defined as constants at the top for easy modification.
* **`inspect_image` Function:** This central function brings together all the steps: loading, preprocessing, comparing, defect detection, and decision-making. It returns a tuple containing the inspection result, SSIM score, defect area percentage, and the image with defect contours (if any).
* **Overlaying Defect Mask:** The code now correctly overlays the detected defect mask (contoured_image) onto the original test image, making it easy to visualize the detected defects. A transparency (`alpha`) is added to the overlay, which enhances visualization.
* **Image Saving:** The code saves the inspection result image (with contours) to a file. This allows you to review the results and fine-tune the parameters. The output filename is based on the input filename, making it easier to track. Added a check to avoid saving if `inspection_result_image` is None (due to a loading error).
* **Comments and Docstrings:** Added comprehensive comments and docstrings to explain the code.
* **Test Image Directory:** Uses a `TEST_IMAGE_DIRECTORY` variable, making it easy to specify where the test images are located.
* **File Extension Handling:** The `main()` function now correctly handles different image file extensions (.jpg, .jpeg, .png) using `endswith()`.
* **Dependency Management:** The code imports all necessary libraries at the beginning.
* **Clear Output:** The code prints informative messages to the console, indicating whether each image is accepted or rejected, and the reason.
* **SSIM Error Handling:** Adds handling if `ssim` fails to return a difference image. This prevents crashes.
* **Adjustable Parameters:** Key parameters like threshold values, blurring kernel size, morphological operation kernel size, and SSIM acceptance threshold are defined as variables, making it easy to experiment and optimize the code for different food products.
* **Correctness:** The logic for defect detection is now significantly improved by inverting the threshold, using morphological operations, and drawing contours on the original image for clear visualization.
* **Readability:** Consistent code style and clear variable names.
How to Use:
1. **Install Libraries:**
```bash
pip install opencv-python scikit-image numpy
```
2. **Prepare Images:**
- Place your reference image in the same directory as the script and name it `reference_product.jpg` (or change `REFERENCE_IMAGE_PATH` accordingly). The reference image should be a high-quality image of a perfect or acceptable product.
- Create a directory named `test_images` in the same directory as the script. Place the test images (images of products to be inspected) in this directory.
3. **Adjust Parameters:**
- **`ACCEPTANCE_THRESHOLD_SSIM`:** Experiment with different values for this threshold. A higher value will make the system more strict. Start around 0.85 and adjust up or down based on your results.
- **`ACCEPTANCE_THRESHOLD_DEFECT_AREA`:** This is the maximum percentage of the image that can be covered by defects while still being considered acceptable. Start with 0.05 (5%) and adjust.
- **Gaussian Blur Kernel:** Adjust the `(5, 5)` in `cv2.GaussianBlur` if your images are very noisy or very sharp. Larger kernels blur more.
- **Threshold Value in `detect_defects`:** The initial value of `25` might need to be adjusted based on the lighting conditions and the noise levels in your images. Lowering the threshold makes it more sensitive to defects.
- **Morphological Operations Kernel:** Adjust the `(5, 5)` in the morphological operations (`cv2.morphologyEx`) to clean up the thresholded image.
4. **Run the Script:**
```bash
python your_script_name.py
```
5. **Review Results:**
- The script will print the inspection results to the console (accepted/rejected, SSIM score, defect area).
- The script will save the inspection result images (with defect contours) to the `test_images` directory. Review these images to verify the accuracy of the defect detection and to fine-tune the parameters.
Important Considerations:
* **Lighting:** Consistent and uniform lighting is crucial for image recognition. Variations in lighting can significantly affect the SSIM score and the accuracy of defect detection. Consider using controlled lighting in your production environment.
* **Image Alignment:** The images of the products must be aligned consistently. Misalignment can lead to false positives (detecting defects where there are none). Consider using image registration techniques to align the images before comparison.
* **Training Data:** For more complex defect detection tasks, consider using machine learning techniques. This would require a training dataset of images with and without defects.
* **Computational Cost:** The code can be computationally intensive, especially for large images. Consider optimizing the code for performance if necessary.
* **Calibration:** Regularly calibrate the system by inspecting known good and bad products and adjusting the thresholds as needed.
This improved version provides a much more robust and accurate solution for automated quality control in food production lines using image recognition. Remember to tailor the parameters to your specific product and environment.
👁️ Viewed: 5
Comments