如何用OpenCV生成网格及药盒单元格的内部区域掩码?
Hey there! Let's work through your two mask-generation questions—they're actually pretty similar once you shift away from Hough Transform for the pill box task, which makes sense since Hough can be finicky with real-world camera noise and imperfect lines.
For general white-line grid images, the key is to isolate the grid lines, then extract the enclosed regions. Here's a step-by-step approach:
Preprocess the image
First, convert to grayscale and use adaptive thresholding to handle uneven lighting (way more reliable than fixed thresholds):import cv2 import numpy as np img = cv2.imread("grid_image.jpg") gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # Adaptive threshold to turn white lines into white foreground, dark background binary = cv2.adaptiveThreshold( gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 2 )Clean up the grid lines
Use morphological operations to fix broken lines and remove small noise:# Create a rectangular structuring element kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3)) # Close operation fills gaps in lines; open operation removes tiny noise closed = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel, iterations=2) opened = cv2.morphologyEx(closed, cv2.MORPH_OPEN, kernel)Extract internal region masks
Find contours in the cleaned grid, then filter out the outer grid frame and fill the internal cells:contours, hierarchy = cv2.findContours(opened, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) mask = np.zeros_like(gray) # Filter contours to keep only internal cells (adjust area ranges for your image) for cnt in contours: area = cv2.contourArea(cnt) # Skip the outer frame (too large) and tiny noise (too small) if 800 < area < (gray.shape[0] * gray.shape[1]) // 2: cv2.drawContours(mask, [cnt], -1, 255, thickness=cv2.FILLED)The
maskvariable now holds the filled internal regions of your grid.
Hough Transform struggles with real-world camera images (noise, distorted lines, uneven lighting from your Raspberry Pi cam). Let's swap to a contour-based approach that's more robust:
Preprocess for camera images
Add Gaussian blur to reduce camera noise before thresholding:# Capture image from Pi cam (or load from file) # img = pi_cam.capture() img = cv2.imread("pill_box.jpg") gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) blur = cv2.GaussianBlur(gray, (5, 5), 0) # Adaptive threshold to handle shadows/lighting changes binary = cv2.adaptiveThreshold( blur, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 15, 4 )Isolate the pill box first
Crop out the pill box from the background to eliminate distractions:# Find the largest contour (should be the pill box frame) contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) pill_box_contour = max(contours, key=cv2.contourArea) # Create a mask for the pill box and crop the ROI box_mask = np.zeros_like(gray) cv2.drawContours(box_mask, [pill_box_contour], -1, 255, cv2.FILLED) pill_box_roi = cv2.bitwise_and(gray, gray, mask=box_mask)Extract individual cell masks
Now find contours inside the pill box ROI, filtering for cells of similar size:# Re-threshold the ROI for cleaner cell lines _, roi_binary = cv2.threshold(pill_box_roi, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) cell_contours, _ = cv2.findContours(roi_binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) cell_masks = [] # Adjust area range to match your pill cells' actual size for cnt in cell_contours: area = cv2.contourArea(cnt) if 600 < area < 2200: cell_mask = np.zeros_like(pill_box_roi) cv2.drawContours(cell_mask, [cnt], -1, 255, cv2.FILLED) cell_masks.append(cell_mask)Check for pills in each cell
Use the masks to compare cell content against empty background (adjust the mean threshold based on your pill/box colors):for idx, cell_mask in enumerate(cell_masks): # Extract only the cell's pixels cell_pixels = cv2.bitwise_and(pill_box_roi, pill_box_roi, mask=cell_mask) # Calculate mean brightness of the cell mean_brightness = cv2.mean(cell_pixels, mask=cell_mask)[0] # Empty cells will be close to the box's background brightness; pills will differ if mean_brightness < 140: # Tweak this value for your setup print(f"Cell {idx+1} has a pill!") else: print(f"Cell {idx+1} is empty.")
Pro Tip for Regular Pill Boxes
If your pill box is a perfect grid (e.g., 2x7), use perspective transformation first to straighten the box into a rectangle. Then you can split the image into equal-sized cells directly (no contour hunting needed)—this is super reliable if you can detect the box's four corner points.
内容的提问来源于stack exchange,提问作者quantumbutterfly

