I recommend you to:
- do camera calibration to make the lines in your image straight, and
- align your camera well to your production line so you can simplify the image processing and make it more robust, and
- use better illumination conditions if possible.
Given the images above, here is a brute force solution:
#!/usr/bin/python3
# -*- coding: utf-8 -*-
import cv2
import numpy as np
# Ref: https://stackoverflow.com/questions/37177811
def crop_minAreaRect(img, rect):
box = cv2.boxPoints(rect)
box = np.int0(box)
W = rect[1][0]
H = rect[1][1]
Xs = [i[0] for i in box]
Ys = [i[1] for i in box]
x1 = min(Xs)
x2 = max(Xs)
y1 = min(Ys)
y2 = max(Ys)
angle = rect[2]
rotated = False
if angle < -45:
angle += 90
rotated = True
# Center of rectangle in source image
center = ((x1+x2)/2,(y1+y2)/2)
# Size of the upright rectangle bounding the rotated rectangle
size = (x2-x1, y2-y1)
M = cv2.getRotationMatrix2D((size[0]/2, size[1]/2), angle, 1.0)
# Cropped upright rectangle
cropped = cv2.getRectSubPix(img, size, center)
cropped = cv2.warpAffine(cropped, M, size)
croppedW = W if not rotated else H
croppedH = H if not rotated else W
# Final cropped & rotated rectangle
croppedRotated = cv2.getRectSubPix(cropped, (int(croppedW),int(croppedH)), (size[0]/2, size[1]/2))
return croppedRotated
def ROI(img):
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
lower_hsv = np.array([0, 0, 135]) # 0, 0, 135
higher_hsv = np.array([180, 20, 240]) # 180, 20, 240
mask = cv2.inRange(hsv, lower_hsv, higher_hsv)
seg = cv2.bitwise_and(img, img, mask=mask)
seg_gray = cv2.cvtColor(seg, cv2.COLOR_BGR2GRAY)
k1 = 51
seg_gauss = cv2.GaussianBlur(seg_gray, (k1, k1), 0)
seg_th = cv2.threshold(seg_gauss, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]
contours, hierarchy = cv2.findContours(image=seg_th, mode=cv2.RETR_EXTERNAL, method=cv2.CHAIN_APPROX_NONE)
for i, cnt in enumerate(contours):
if cv2.contourArea(cnt)>1000000:
x,y,w,h = cv2.boundingRect(cnt)
rect = cv2.minAreaRect(cnt)
roi = crop_minAreaRect(img, rect)
return roi
- As you reduced your problem to a specific ROI, you can do use edge detection, Morphology, and Filtering as follows:
def ImProc(roi):
gray = cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY)
k_smooth = 5
gauss = cv2.GaussianBlur(gray, (k_smooth, k_smooth), 0)
# # Canny
edges = cv2.Canny(gauss,0,255)
k = 31 #300
kernel = np.ones((k,k),np.uint8)
mph = cv2.morphologyEx(edges, cv2.MORPH_CLOSE, kernel)
res = cv2.medianBlur(mph, 21)
return res
I have applied this approach to the given images:
empty = cv2.imread("empty.jpg")
full = cv2.imread("full.jpg")
roi_empty = ROI(empty)
roi_full = ROI(full)
res_empty = ImProc(roi_empty)
res_full = ImProc(roi_full)
cv2.namedWindow("res_full", cv2.WINDOW_NORMAL)
cv2.imshow("res_full", res_full)
cv2.waitKey(0)
and I have got the following result for the full tray (rotated):

Note that this solution is subject to different parameters, to better control them and get stable results I advise you to consider the recommendations above.