Last active
August 4, 2020 15:58
-
-
Save crackwitz/91b1f6540c66778c489967ca815e9d9d to your computer and use it in GitHub Desktop.
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
#!/usr/bin/env python3 | |
import sys | |
import numpy as np | |
import cv2 as cv | |
from scipy.interpolate import interp2d | |
def build_transform(p0, p1, stride=None, nsamples=None): | |
"builds an affine transform with x+ along defined line" | |
# use one of stride (in pixels) or nsamples (absolute value) | |
(x0, y0) = p0 | |
(x1, y1) = p1 | |
dx = x1 - x0 | |
dy = y1 - y0 | |
length = np.hypot(dx, dy) | |
if nsamples is not None: | |
#stride = length / nsamples | |
factor = 1 / nsamples | |
else: | |
if stride is None: | |
stride = 1.0 | |
factor = stride / length | |
nsamples = int(round(length / stride)) | |
# map: src <- dst (use WARP_INVERSE_MAP flag for warpAffine) | |
H = np.eye(3, dtype=np.float64) | |
H[0:2, 0] = (dx, dy) # x unit vector | |
H[0:2, 1] = (-dy, dx) # y unit vector is x rotated by 90 degrees | |
H[0:2, 0:2] *= factor | |
H[0:2, 2] = (x0, y0) # translate onto starting point | |
A = H[0:2, :] # take affine part | |
# number of samples along line | |
return (nsamples, A) | |
print("loading picture...", end=" ", flush=True) | |
im = cv.imread(sys.argv[1] if len(sys.argv) >= 2 else "200801-2056.png", cv.IMREAD_GRAYSCALE) | |
imh, imw = im.shape[:2] | |
im = 255-im # invert | |
im = im.astype(np.float32) | |
print("done") | |
# define a line segment to sample along | |
p0, p1 = (81, 143), (81, 108) | |
stride = 1/64 # sample stride in pixels | |
# build transform | |
nsamples, M = build_transform(p0, p1, stride=stride) | |
print(f"taking {nsamples} samples along line {p0} -> {p1}") | |
if False: | |
# INTER_CUBIC seems to break down beyond 1/32 sampling (discretizes). there might be fixed point algorithms at work | |
# use transform to get samples | |
samples = cv.warpAffine(im, M=M, dsize=(nsamples, 1), flags=cv.WARP_INVERSE_MAP | cv.INTER_CUBIC) | |
# data is a row vector | |
samples = samples[0] | |
#samples = samples.astype(np.int16) | |
# opencv seems to use fixed point arithmetic that produces significant error | |
# either that or the implementation is broken | |
# https://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.interp2d.html | |
sampler = interp2d(x=np.arange(imw), y=np.arange(imh), z=im, kind='cubic') | |
sampler = np.vectorize(sampler) # doesn't cake coordinate pairs as is, vectorize() handles that (!= execution speed!) | |
coords = np.vstack([np.arange(nsamples), np.zeros(nsamples), np.ones(nsamples)]) | |
coords2 = M.astype(np.float32) @ coords # @ = np.dot | |
samples2 = sampler(*coords2) | |
samples = samples2 | |
# two variants: | |
# (1) get gradient, find minimum (falling edge) and maximum (rising edge) | |
# (2) use levels, find first black sample from both ends | |
# off-by-half because for values [0,1,1,0] this returns [+1,0,-1] | |
gradient = np.diff(samples) / stride | |
i_falling = np.argmin(gradient) # in samples | |
i_rising = np.argmax(gradient) # in samples | |
distance = (i_rising - i_falling) * stride # in pixels | |
## np.argmin finds the smallest element, and if there are multiple smallest, the first one | |
## find first black pixel from one end | |
## find last white pixel from other end (first black + 1) | |
#thresholded = (samples >= 128) | |
#v1 = np.argmin(thresholded) | |
#v2 = len(thresholded) - np.argmin(thresholded[::-1]) | |
print(f"distance: {distance} pixels") | |
plot = cv.plot.Plot2d_create(np.arange(nsamples, dtype=np.float64), samples.astype(np.float64)) | |
plot.setMinY(256+32) | |
plot.setMaxY(-32) | |
plot.setMinX(0) | |
plot.setMaxX(nsamples) | |
plot.setGridLinesNumber(5) | |
plot.setShowText(False) # callout for specific point, setPointIdxToPrint(index) | |
plot.setPlotGridColor((64,)*3) | |
canvas1 = plot.render() | |
plot = cv.plot.Plot2d_create(np.arange(nsamples-1) + 0.5, gradient.astype(np.float64)) | |
plot.setMinY(256+64) | |
plot.setMaxY(-256-64) | |
plot.setMinX(0) | |
plot.setMaxX(nsamples) | |
plot.setGridLinesNumber(5) | |
plot.setShowText(False) # callout for specific point, setPointIdxToPrint(index) | |
plot.setPlotGridColor((64,)*3) | |
canvas2 = plot.render() | |
canvas = np.vstack([canvas1, canvas2]) # 600 wide, 800 tall | |
# plots are 600x400 pixels... and there's no way to plot multiple or plot lines in "plot space" | |
px_falling = int(600 * (i_falling+0.5) / nsamples) | |
px_rising = int(600 * (i_rising+0.5) / nsamples) | |
cv.line(canvas, (px_falling, 0), (px_falling, 400*2), color=(255,0,0)) | |
cv.line(canvas, (px_rising, 0), (px_rising, 400*2), color=(255,0,0)) | |
cv.imwrite("plot.png", canvas) | |
cv.imshow("plot", canvas) | |
cv.waitKey(-1) | |
if 0: | |
# let's draw something | |
canvas = cv.cvtColor(im, cv.COLOR_GRAY2BGR) | |
canvas = cv.resize(canvas, dsize=None, fx=4, fy=4, interpolation=cv.INTER_NEAREST) | |
cv.line(canvas, (sx*4+2, sy0*4+2), (sx*4+2, sy1*4+2), color=(0,0,255)) | |
cv.line(canvas, ((sx-2)*4+2, (sy0+v1)*4+2), ((sx+2)*4+2, (sy0+v1)*4+2), color=(0, 255, 255)) | |
cv.line(canvas, ((sx-2)*4+2, (sy0+v2)*4+2), ((sx+2)*4+2, (sy0+v2)*4+2), color=(255, 0, 0)) | |
cv.imshow("canvas", canvas) | |
cv.waitKey(-1) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment