车内德国机动车牌照轮廓区域识别技术难题求助
Hey there, let's work through those three tricky issues you're hitting with detecting German license plates using cv2.findContours and cv2.HoughLinesP. I've messed around with similar computer vision problems before, so here's how I'd approach each problem:
1. Hand occlusion breaking contour continuity
Hands covering parts of the plate are a classic pain point. Here's what you can try:
- Color-based pre-filtering: German plates are typically white/yellow with black text, which has a huge color contrast with skin tones. Use
cv2.inRange()to mask out skin-colored regions first—this will remove the hand from your edge detection pipeline entirely before you even look for contours. - Morphological closing: Apply a closing operation (
cv2.morphologyEx()withcv2.MORPH_CLOSE) to your edge image. This fills small gaps in contours caused by partial occlusion, helping reconnect broken plate edges. - Contour approximation: When extracting contours, use
cv2.approxPolyDP()with a small epsilon value. This can smooth out minor breaks and help you identify the plate's rectangular shape even if parts are covered.
2. False contours from similar in-car objects (like radios)
To filter out these distractions, lean into the unique properties of German license plates:
- Aspect ratio check: German standard plates have a fixed aspect ratio (~4.7:1, since they're 520mm wide × 110mm tall). After getting bounding rectangles from contours, calculate
w/hand discard any regions that don't fall within a tight range around this ratio (e.g., 4.5–5.0). - Line angle filtering: For
HoughLinesP, add logic to only keep lines that are nearly horizontal or vertical (since plates are rectangles). Calculate each line's angle withmath.atan2(l[3]-l[1], l[2]-l[0]), convert to degrees, and keep lines within ±10° of 0° or 90°. This will eliminate random lines from radios or other non-plate objects. - Character presence check: Run a lightweight OCR (like Tesseract with a custom trained data set for license plates) on candidate regions. If a region doesn't contain alphanumeric characters that match German plate patterns (e.g., 1-3 letters, followed by 1-4 numbers, then optional letters), discard it.
3. Partial plate (off-image) leading to incorrect cropping
When the plate is cut off, relying on cv2.boundingRect() of the largest contour won't work because the contour is incomplete. Try these fixes:
- Infer plate boundaries from detected lines: Instead of using contours, collect all valid horizontal/vertical lines from
HoughLinesP. Find the topmost, bottommost leftmost, and rightmost lines (even if they don't connect), then use those four lines to define your cropping region. For example:- Top boundary: the y-coordinate of the highest horizontal line
- Bottom boundary: the y-coordinate of the lowest horizontal line
- Left/right boundaries: the x-coordinates of the leftmost/rightmost vertical lines
- Use minimum area rectangles: Replace
cv2.boundingRect()withcv2.minAreaRect()on your candidate contours. This function creates a rotated rectangle that fits the contour as closely as possible, even if parts of the plate are off-image. You can then convert this to a bounding box withcv2.boxPoints(). - Prioritize color regions during cropping: Combine your line/contour detection with the color mask from step 1. When defining the crop area, make sure it overlaps significantly with the plate-colored region—this prevents you from cropping to a radio just because it has similar lines.
Quick Code Tweaks to Try
Here's how you might adjust your existing code to incorporate some of these ideas:
import cv2 import math def crop_image(dilated): # Step 1: Mask out skin tones (adjust ranges for your lighting conditions) hsv = cv2.cvtColor(dilated, cv2.COLOR_BGR2HSV) lower_skin = (0, 20, 70) upper_skin = (20, 255, 255) skin_mask = cv2.inRange(hsv, lower_skin, upper_skin) no_skin = cv2.bitwise_and(dilated, dilated, mask=~skin_mask) binary = cv2.threshold(no_skin, 150, 255, cv2.THRESH_BINARY)[1] # Apply closing to fix small contour gaps from occlusion kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5,5)) closed = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel) edges = cv2.Canny(closed, 150, 84, apertureSize=3) cv2.imshow("canny", edges) dilted_lines = dilated.copy() lines = cv2.HoughLinesP(edges, 1, math.pi / 180, 100, minLineLength=15, maxLineGap=500) # Filter lines to keep only horizontal/vertical candidates valid_lines = [] if lines is not None: for line in lines: l = line[0] angle = math.degrees(math.atan2(l[3]-l[1], l[2]-l[0])) # Keep lines within 10 degrees of horizontal or vertical if (abs(angle) < 10 or abs(angle - 90) < 10 or abs(angle +90) <10): valid_lines.append(l) cv2.line(dilted_lines, (l[0], l[1]), (l[2], l[3]), (0, 255, 0), 4) cv2.imshow("lines", dilted_lines) # Filter contours by German plate aspect ratio contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) plate_candidates = [] for cnt in contours: x, y, w, h = cv2.boundingRect(cnt) aspect_ratio = w / float(h) # Check if aspect ratio falls within German plate's typical range if 4.5 <= aspect_ratio <= 5.0: plate_candidates.append(cnt) if plate_candidates: # Sort candidates by area and pick the largest valid one sortedCnt = sorted(plate_candidates, key=lambda x: cv2.contourArea(x)) x, y, w, h = cv2.boundingRect(sortedCnt[-1]) cv2.imshow("crop", dilated[y:y + h, x:x + w]) else: # Fallback: infer crop area from valid lines if no contour matches if valid_lines: xs = [] ys = [] for l in valid_lines: xs.extend([l[0], l[2]]) ys.extend([l[1], l[3]]) x_min, x_max = min(xs), max(xs) y_min, y_max = min(ys), max(ys) cv2.imshow("crop", dilated[y_min:y_max, x_min:x_max]) # Call with your dilated input image # crop_image(your_dilated_image)
内容的提问来源于stack exchange,提问作者shalama

