Skip to content

Instantly share code, notes, and snippets.

@dimitrs
Created June 15, 2014 13:32
Show Gist options
  • Save dimitrs/702d05be879fa5a23d2b to your computer and use it in GitHub Desktop.
Save dimitrs/702d05be879fa5a23d2b to your computer and use it in GitHub Desktop.
Detect and flatten a label on a jar. Use both OpenCV-Python and SciKit-image since both are based on Numpy.
#Import both skimage and cv
from skimage import transform as tf
from skimage import io
import cv2
import numpy as np
from scipy import optimize
import matplotlib.pyplot as plt
# Could use either skimage or cv to read the image
# img = io.imread('jar.png')
img = cv2.imread('jar.png')
gray_image = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray_image,127,255,cv2.THRESH_BINARY)
edges = cv2.Canny(thresh ,100, 200)
# Find largest contour (should be the label)
contours,hierarchy = cv2.findContours(edges, 0, 1)
areas = [cv2.contourArea(c) for c in contours]
max_index = np.argmax(areas)
cnt=contours[max_index]
# Create a mask of the label
mask = np.zeros(img.shape,np.uint8)
cv2.drawContours(mask, [cnt],0,255,-1)
# Find the 4 borders
scale = 1
delta = 0
ddepth = cv2.CV_8U
borderType=cv2.BORDER_DEFAULT
left = cv2.Sobel(mask,ddepth,1,0,ksize=1,scale=1,delta=0,borderType=borderType)
right = cv2.Sobel(mask,ddepth,1,0,ksize=1,scale=-1,delta=0, borderType=borderType)
top = cv2.Sobel(mask,ddepth,0,1,ksize=1,scale=1,delta=0,borderType=borderType)
bottom = cv2.Sobel(mask,ddepth,0,1,ksize=1,scale=-1,delta=0,borderType=borderType)
# Remove noise from borders
kernel = np.ones((2,2),np.uint8)
left_border = cv2.erode(left,kernel,iterations = 1)
right_border = cv2.erode(right,kernel,iterations = 1)
top_border = cv2.erode(top,kernel,iterations = 1)
bottom_border = cv2.erode(bottom,kernel,iterations = 1)
# Equations 1 and 2: c1 + c2*x + c3*y + c4*x*y, c5 + c6*y + c7*x + c8*x^2
# Find coeficients c1,c2,c3,c4,c5,c6,c7,c8 by minimizing the error function.
# Points on the left border should be mapped to (0,anything).
# Points on the right border should be mapped to (108,anything)
# Points on the top border should be mapped to (anything,0)
# Points on the bottom border should be mapped to (anything,70)
sum_of_squares_y = '+'.join( [ "(c[0]+c[1]*%s+c[2]*%s+c[3]*%s*%s)**2" % \
(x,y,x,y) for y,x,z in np.transpose(np.nonzero(left_border)) ])
sum_of_squares_y += " + "
sum_of_squares_y += '+'.join( [ "(-108+c[0]+c[1]*%s+c[2]*%s+c[3]*%s*%s)**2" % \
(x,y,x,y) for y,x,z in np.transpose(np.nonzero(right_border)) ])
res_y = optimize.minimize(lambda c: eval(sum_of_squares_y),(0,0,0,0),method='SLSQP')
sum_of_squares_x = '+'.join( [ "(-70+c[0]+c[1]*%s+c[2]*%s+c[3]*%s*%s)**2" % \
(y,x,x,x) for y,x,z in np.transpose(np.nonzero(bottom_border)) ] )
sum_of_squares_x += " + "
sum_of_squares_x += '+'.join( [ "(c[0]+c[1]*%s+c[2]*%s+c[3]*%s*%s)**2" % \
(y,x,x,x) for y,x,z in np.transpose(np.nonzero(top_border)) ] )
res_x = optimize.minimize(lambda c: eval(sum_of_squares_x),(0,0,0,0), method='SLSQP')
# Map the image using equatinos 1 and 2 (coeficients c1...c8 in res_x and res_y)
def map_x(res, coord):
return res[0] + res[1]*coord[1] + res[2]*coord[0] + res[3]*coord[1]*coord[0]
def map_y(res, coord):
return res[0] + res[1]*coord[0] + res[2]*coord[1] + res[3]*coord[1]*coord[1]
flattened = np.zeros(img.shape, img.dtype)
for y,x,z in np.transpose(np.nonzero(mask)):
new_y = map_y(res_x.x,[y,x])
new_x = map_x(res_y.x,[y,x])
flattened[float(new_y)][float(new_x)] = img[y][x]
# Crop the image
flattened = flattened[0:70, 0:105]
# Alternatively, use PiecewiseAffineTransform from SciKit-image to transform the image
leftmost = tuple(cnt[cnt[:,:,0].argmin()][0])
rightmost = tuple(cnt[cnt[:,:,0].argmax()][0])
topmost = tuple(cnt[cnt[:,:,1].argmin()][0])
bottommost = tuple(cnt[cnt[:,:,1].argmax()][0])
dst = list()
src = list()
for y,x,z in np.transpose(np.nonzero(top_border)):
dst.append([x,y])
src.append([x,topmost[1]])
for y,x,z in np.transpose(np.nonzero(bottom_border)):
dst.append([x,y])
src.append([x,bottommost[1]])
for y,x,z in np.transpose(np.nonzero(left_border)):
dst.append([x,y])
src.append([leftmost[0],y])
for y,x,z in np.transpose(np.nonzero(right_border)):
dst.append([x,y])
src.append([rightmost[0],y])
src = np.array(src)
dst = np.array(dst)
tform3 = tf.PiecewiseAffineTransform()
tform3.estimate(src, dst)
warped = tf.warp(img, tform3, order=2)
warped = warped[85:170, 31:138]
@dimitrs
Copy link
Author

dimitrs commented Jun 15, 2014

@bobbyjayblack
Copy link

Would you mind updating this to work with python 3.6?
I tried running it with jar.png and the latest apt-get and pip versions of the requirements and received the following errors:
https://dpaste.de/8MSi

=== Error 1 ===
Traceback (most recent call last):
File "flatten_label.py", line 75, in
flattened[float(new_y)][float(new_x)] = img[y][x]
Fixed by changing that line 75 to:
flattened[int(new_y)][int(new_x)] = img[y][x]

Reran the program and received the error shown in the pastebin.

Thank you.

@balraj111
Copy link

@bobbyjayblack change flattened[float(new_y)][float(new_x)] = img[y][x] to flattened[int(new_y)][int(new_x)] = img[y][x]

@mo10serek
Copy link

When I run your code I got this error:

C:/Users/Michael Balcerzak/Documents/opencv-text-detection/opencv-text-detection/ImageFlating.py:103: UserWarning: Bi-quadratic interpolation behavior has changed due to a bug in the implementation of scikit-image. The new version now serves as a wrapper around SciPy's interpolation functions, which itself is not verified to be a correct implementation. Until skimage's implementation is fixed, we recommend to use bi-linear or bi-cubic interpolation instead.
warped = tf.warp(img, tform3, order=2)

How to fix it?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment