Skip to content

Instantly share code, notes, and snippets.

@petrblahos
Created December 20, 2017 10:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save petrblahos/49643eabfd4490a8d45410517a924e34 to your computer and use it in GitHub Desktop.
Save petrblahos/49643eabfd4490a8d45410517a924e34 to your computer and use it in GitHub Desktop.
Shape interpolation, as described in http://petr.blahos.com/blog/shape-warping/
import cv2
import numpy as np
import scipy.spatial.distance as distance
def get_interpolated_points(c1, c2, step, scale):
"""
Performs a linear interpolation between c1 and c2. Draws a line between
members of c1 and c2 on the same index, divides the line between the
points into "scale" sections and returns the step-th point.
:param c1: A list of points (may be a contour from cv2.getContours).
:param c2: A list of the same length (number of points) as c1.
:param step: The step of the interpolation on the scale from (0 to scale>.
:param scale: The length of the scale.
:returns: A list of points interpolated between c1 and c2.
get_interpolated_points(np.array([[0,0], [10,0]]),
np.array([[10,0], [0,0]]), 1, 10) --> [[1, 0], [9, 0]]
get_interpolated_points(np.array([[0,0], [10,0]]),
np.array([[10,0], [0,0]]), 9, 10) --> [[9, 0], [1, 0]]
get_interpolated_points(np.array([[0,0], [10,0]]),
np.array([[10,0], [0,0]]), 10, 10) --> [[10, 0], [0, 0]]
"""
c1 = c1.reshape(c1.shape[0], c1.shape[-1])
c2 = c2.reshape(c2.shape[0], c2.shape[-1])
dif = c2 - c1
return np.array(c1 + dif*step/scale, dtype=np.int32)
def get_contour(img, height):
if 3 == len(img.shape):
img = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
thr = cv2.threshold(img, 0, 255,
cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
cnts = cv2.findContours(thr, cv2.RETR_TREE,
cv2.CHAIN_APPROX_SIMPLE)[1]
ret = None
for (idx, i) in enumerate(sorted(cnts, key=cv2.contourArea, reverse=1)):
ret = cv2.approxPolyDP(i, height/600, True)
break
return ret
def enlarge_contour(c1, point_count):
"""
Returns a contour that has point_count points.
:param c1: A contour that has at most point_count points. If c1 has
the same number or more of points as point_count, returns c1.
:param point_count:
:returns: A new contour that has all points from c1 and new
points obtained by linear interpolation between the points
with the biggest distance.
"""
to_add = point_count - c1.shape[0]
if to_add <= 0:
return c1
c1 = c1.reshape((c1.shape[0], c1.shape[-1]))
dists = []
for (idx, i) in enumerate(c1[1:]):
dists.append((distance.euclidean(c1[idx], i), idx))
dists.sort(reverse=1)
# interpolate between c1[i0], c1[i0+1]
add_idxs = []
add_items = []
for i in range(min(len(dists)//2, to_add)):
i0 = dists[i][1]
new_pt = c1[i0] + (c1[i0+1] - c1[i0])/2
add_idxs.append(i0+1)
add_items.append(new_pt)
c1 = np.insert(c1, add_idxs, add_items, 0)
return enlarge_contour(c1, point_count)
def determine_font_scale(img_size):
"""
Returns about the biggest font scale to fit a single character
into the supplied img_size.
"""
scale = 1.0
for i in range(10):
(sz, baseline) = cv2.getTextSize("M", cv2.FONT_HERSHEY_SIMPLEX, scale, 4)
if sz[1] + baseline < img_size[0]*0.9:
scale *= (img_size[0]*0.9)/(sz[1]+baseline)
elif sz[1] + baseline > img_size[0]*0.95:
scale *= (img_size[0]*0.9)/(sz[1]+baseline)
else:
break
return (scale, baseline)
def gen_char(img_size, c, scale, baseline):
"""
Creates an image containing a centered character c.
"""
img = np.zeros(img_size, dtype=np.uint8)
(sz, _baseline) = cv2.getTextSize(c, cv2.FONT_HERSHEY_SIMPLEX,
scale, 4)
cv2.putText(img, c, ((img_size[1]-sz[0])//2, img_size[0] - baseline),
cv2.FONT_HERSHEY_SIMPLEX, scale, 40, cv2.LINE_AA)
return img
def interpolate_shapes(img_size, i0, i1):
out_shape = img_size
c1 = get_contour(i0, out_shape[1])
c2 = get_contour(i1, out_shape[1])
c1 = c1.reshape(c1.shape[0], c1.shape[-1])
c2 = c2.reshape(c2.shape[0], c2.shape[-1])
if c1.shape[0] < c2.shape[0]:
c1 = enlarge_contour(c1, c2.shape[0])
else:
c2 = enlarge_contour(c2, c1.shape[0])
c_fin = c2
SCALE = 50
for i in range(SCALE + 1):
c2 = get_interpolated_points(c1, c_fin, i, SCALE)
img = np.zeros((out_shape[0], out_shape[1]), dtype=np.uint8)
cv2.drawContours(img, [c2], -1, 255, 50)
cv2.imshow("A", img)
cv2.waitKey(1)
if "__main__" == __name__:
img_size = (600, 600)
(scale, baseline) = determine_font_scale(img_size)
i0 = gen_char(img_size, 'A', scale, baseline)
i1 = gen_char(img_size, 'O', scale, baseline)
interpolate_shapes(img_size, i0, i1)
cv2.waitKey(0)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment