Created
January 14, 2018 09:45
-
-
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/
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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