Created
December 20, 2017 10:05
-
-
Save petrblahos/49643eabfd4490a8d45410517a924e34 to your computer and use it in GitHub Desktop.
Shape interpolation, as described in http://petr.blahos.com/blog/shape-warping/
This file contains 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): | |
""" | |
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