Skip to content

Instantly share code, notes, and snippets.

@petrblahos
Created January 14, 2018 09:45
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/2a208b135cc9e62ceed1adac8ab66ab1 to your computer and use it in GitHub Desktop.
Save petrblahos/2a208b135cc9e62ceed1adac8ab66ab1 to your computer and use it in GitHub Desktop.
Shape interpolation using whole contours, as described in http://petr.blahos.com/blog/shape-warping-03/
import cv2
import numpy as np
import scipy.spatial.distance as distance
def get_interpolated_points(c1, c2, step, scale):
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_sorted_shape_list(img, height, simplified):
"""
Returns the shapes sorted from the longest to the shortest. Skips
the shapes consising of just one point.
"""
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_LIST,
cv2.CHAIN_APPROX_SIMPLE)[1]
if simplified:
cnts = [cv2.approxPolyDP(i, height/200, True) for i in cnts]
cnts.sort(key=lambda x: cv2.arcLength(x, True), reverse=True)
return [i for i in cnts if i.shape[0] > 1]
def extend_list(sh_list, count):
"""
Duplicate items in the sh_list to make it count items long.
:param sh_list: A list to duplicate some items in.
:param count: The resulting list will have as many items as source_sh.
:returns: A list such as len(returned_list) == count
"""
src_points_count = len(sh_list)
smaller = count // src_points_count
rep_schema = np.full([src_points_count], smaller)
rep_schema[0:src_points_count-(smaller+1)*src_points_count+count] = smaller+1
return np.repeat(sh_list, rep_schema, axis=0)
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(max(1, 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(out_shape, i0, i1):
SIMPLIFIED = False
c1l = get_sorted_shape_list(i0, out_shape[1], simplified=SIMPLIFIED)
c2l = get_sorted_shape_list(i1, out_shape[1], simplified=SIMPLIFIED)
# make the shapes the same length
if len(c1l) < len(c2l):
c1l = extend_list(c1l, len(c2l))
elif len(c2l) < len(c1l):
c2l = extend_list(c2l, len(c1l))
pairs = []
# now we need all shapes to have the same length
for i in range(len(c1l)):
c1 = c1l[i]
c2 = c2l[i]
if len(c1) > len(c2):
c2 = enlarge_contour(c2, len(c1))
elif len(c2) > len(c1):
c1 = enlarge_contour(c1, len(c2))
pairs.append((c1, c2))
SCALE = 100
for j in range(SCALE + 1):
img = np.zeros((out_shape[0], out_shape[1], 3), dtype=np.uint8)
for (c1, c2) in pairs:
cf = get_interpolated_points(c1, c2, j, SCALE)
cv2.drawContours(img, [cf], 0, (255, 0, 255), 4)
cv2.imshow("A", img)
cv2.waitKey(1)
return img
if "__main__" == __name__:
img_size = (800, 800)
(scale, baseline) = determine_font_scale(img_size)
i0 = gen_char(img_size, '-', scale, baseline)
for i in "01467&890-ijklmno":
i = ord(i)
img = gen_char(img_size, chr(i), scale, baseline)
out_img = interpolate_shapes(img_size, i0, img)
i0 = img
cv2.waitKey(50)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment