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