Spaces:
Build error
Build error
''' | |
This script is helper function for preprocessing. | |
Most of the code are converted from LayoutNet official's matlab code. | |
All functions, naming rule and data flow follow official for easier | |
converting and comparing. | |
Code is not optimized for python or numpy yet. | |
''' | |
import sys | |
import numpy as np | |
from scipy.ndimage import map_coordinates | |
import cv2 | |
from pylsd import lsd | |
def computeUVN(n, in_, planeID): | |
''' | |
compute v given u and normal. | |
''' | |
if planeID == 2: | |
n = np.array([n[1], n[2], n[0]]) | |
elif planeID == 3: | |
n = np.array([n[2], n[0], n[1]]) | |
bc = n[0] * np.sin(in_) + n[1] * np.cos(in_) | |
bs = n[2] | |
out = np.arctan(-bc / (bs + 1e-9)) | |
return out | |
def computeUVN_vec(n, in_, planeID): | |
''' | |
vectorization version of computeUVN | |
@n N x 3 | |
@in_ MN x 1 | |
@planeID N | |
''' | |
n = n.copy() | |
if (planeID == 2).sum(): | |
n[planeID == 2] = np.roll(n[planeID == 2], 2, axis=1) | |
if (planeID == 3).sum(): | |
n[planeID == 3] = np.roll(n[planeID == 3], 1, axis=1) | |
n = np.repeat(n, in_.shape[0] // n.shape[0], axis=0) | |
assert n.shape[0] == in_.shape[0] | |
bc = n[:, [0]] * np.sin(in_) + n[:, [1]] * np.cos(in_) | |
bs = n[:, [2]] | |
out = np.arctan(-bc / (bs + 1e-9)) | |
return out | |
def xyz2uvN(xyz, planeID=1): | |
ID1 = (int(planeID) - 1 + 0) % 3 | |
ID2 = (int(planeID) - 1 + 1) % 3 | |
ID3 = (int(planeID) - 1 + 2) % 3 | |
normXY = np.sqrt(xyz[:, [ID1]] ** 2 + xyz[:, [ID2]] ** 2) | |
normXY[normXY < 0.000001] = 0.000001 | |
normXYZ = np.sqrt(xyz[:, [ID1]] ** 2 + xyz[:, [ID2]] ** 2 + xyz[:, [ID3]] ** 2) | |
v = np.arcsin(xyz[:, [ID3]] / normXYZ) | |
u = np.arcsin(xyz[:, [ID1]] / normXY) | |
valid = (xyz[:, [ID2]] < 0) & (u >= 0) | |
u[valid] = np.pi - u[valid] | |
valid = (xyz[:, [ID2]] < 0) & (u <= 0) | |
u[valid] = -np.pi - u[valid] | |
uv = np.hstack([u, v]) | |
uv[np.isnan(uv[:, 0]), 0] = 0 | |
return uv | |
def uv2xyzN(uv, planeID=1): | |
ID1 = (int(planeID) - 1 + 0) % 3 | |
ID2 = (int(planeID) - 1 + 1) % 3 | |
ID3 = (int(planeID) - 1 + 2) % 3 | |
xyz = np.zeros((uv.shape[0], 3)) | |
xyz[:, ID1] = np.cos(uv[:, 1]) * np.sin(uv[:, 0]) | |
xyz[:, ID2] = np.cos(uv[:, 1]) * np.cos(uv[:, 0]) | |
xyz[:, ID3] = np.sin(uv[:, 1]) | |
return xyz | |
def uv2xyzN_vec(uv, planeID): | |
''' | |
vectorization version of uv2xyzN | |
@uv N x 2 | |
@planeID N | |
''' | |
assert (planeID.astype(int) != planeID).sum() == 0 | |
planeID = planeID.astype(int) | |
ID1 = (planeID - 1 + 0) % 3 | |
ID2 = (planeID - 1 + 1) % 3 | |
ID3 = (planeID - 1 + 2) % 3 | |
ID = np.arange(len(uv)) | |
xyz = np.zeros((len(uv), 3)) | |
xyz[ID, ID1] = np.cos(uv[:, 1]) * np.sin(uv[:, 0]) | |
xyz[ID, ID2] = np.cos(uv[:, 1]) * np.cos(uv[:, 0]) | |
xyz[ID, ID3] = np.sin(uv[:, 1]) | |
return xyz | |
def warpImageFast(im, XXdense, YYdense): | |
minX = max(1., np.floor(XXdense.min()) - 1) | |
minY = max(1., np.floor(YYdense.min()) - 1) | |
maxX = min(im.shape[1], np.ceil(XXdense.max()) + 1) | |
maxY = min(im.shape[0], np.ceil(YYdense.max()) + 1) | |
im = im[int(round(minY-1)):int(round(maxY)), | |
int(round(minX-1)):int(round(maxX))] | |
assert XXdense.shape == YYdense.shape | |
out_shape = XXdense.shape | |
coordinates = [ | |
(YYdense - minY).reshape(-1), | |
(XXdense - minX).reshape(-1), | |
] | |
im_warp = np.stack([ | |
map_coordinates(im[..., c], coordinates, order=1).reshape(out_shape) | |
for c in range(im.shape[-1])], | |
axis=-1) | |
return im_warp | |
def rotatePanorama(img, vp=None, R=None): | |
''' | |
Rotate panorama | |
if R is given, vp (vanishing point) will be overlooked | |
otherwise R is computed from vp | |
''' | |
sphereH, sphereW, C = img.shape | |
# new uv coordinates | |
TX, TY = np.meshgrid(range(1, sphereW + 1), range(1, sphereH + 1)) | |
TX = TX.reshape(-1, 1, order='F') | |
TY = TY.reshape(-1, 1, order='F') | |
ANGx = (TX - sphereW/2 - 0.5) / sphereW * np.pi * 2 | |
ANGy = -(TY - sphereH/2 - 0.5) / sphereH * np.pi | |
uvNew = np.hstack([ANGx, ANGy]) | |
xyzNew = uv2xyzN(uvNew, 1) | |
# rotation matrix | |
if R is None: | |
R = np.linalg.inv(vp.T) | |
xyzOld = np.linalg.solve(R, xyzNew.T).T | |
uvOld = xyz2uvN(xyzOld, 1) | |
Px = (uvOld[:, 0] + np.pi) / (2*np.pi) * sphereW + 0.5 | |
Py = (-uvOld[:, 1] + np.pi/2) / np.pi * sphereH + 0.5 | |
Px = Px.reshape(sphereH, sphereW, order='F') | |
Py = Py.reshape(sphereH, sphereW, order='F') | |
# boundary | |
imgNew = np.zeros((sphereH+2, sphereW+2, C), np.float64) | |
imgNew[1:-1, 1:-1, :] = img | |
imgNew[1:-1, 0, :] = img[:, -1, :] | |
imgNew[1:-1, -1, :] = img[:, 0, :] | |
imgNew[0, 1:sphereW//2+1, :] = img[0, sphereW-1:sphereW//2-1:-1, :] | |
imgNew[0, sphereW//2+1:-1, :] = img[0, sphereW//2-1::-1, :] | |
imgNew[-1, 1:sphereW//2+1, :] = img[-1, sphereW-1:sphereW//2-1:-1, :] | |
imgNew[-1, sphereW//2+1:-1, :] = img[0, sphereW//2-1::-1, :] | |
imgNew[0, 0, :] = img[0, 0, :] | |
imgNew[-1, -1, :] = img[-1, -1, :] | |
imgNew[0, -1, :] = img[0, -1, :] | |
imgNew[-1, 0, :] = img[-1, 0, :] | |
rotImg = warpImageFast(imgNew, Px+1, Py+1) | |
return rotImg | |
def imgLookAt(im, CENTERx, CENTERy, new_imgH, fov): | |
sphereH = im.shape[0] | |
sphereW = im.shape[1] | |
warped_im = np.zeros((new_imgH, new_imgH, 3)) | |
TX, TY = np.meshgrid(range(1, new_imgH + 1), range(1, new_imgH + 1)) | |
TX = TX.reshape(-1, 1, order='F') | |
TY = TY.reshape(-1, 1, order='F') | |
TX = TX - 0.5 - new_imgH/2 | |
TY = TY - 0.5 - new_imgH/2 | |
r = new_imgH / 2 / np.tan(fov/2) | |
# convert to 3D | |
R = np.sqrt(TY ** 2 + r ** 2) | |
ANGy = np.arctan(- TY / r) | |
ANGy = ANGy + CENTERy | |
X = np.sin(ANGy) * R | |
Y = -np.cos(ANGy) * R | |
Z = TX | |
INDn = np.nonzero(np.abs(ANGy) > np.pi/2) | |
# project back to sphere | |
ANGx = np.arctan(Z / -Y) | |
RZY = np.sqrt(Z ** 2 + Y ** 2) | |
ANGy = np.arctan(X / RZY) | |
ANGx[INDn] = ANGx[INDn] + np.pi | |
ANGx = ANGx + CENTERx | |
INDy = np.nonzero(ANGy < -np.pi/2) | |
ANGy[INDy] = -np.pi - ANGy[INDy] | |
ANGx[INDy] = ANGx[INDy] + np.pi | |
INDx = np.nonzero(ANGx <= -np.pi); ANGx[INDx] = ANGx[INDx] + 2 * np.pi | |
INDx = np.nonzero(ANGx > np.pi); ANGx[INDx] = ANGx[INDx] - 2 * np.pi | |
INDx = np.nonzero(ANGx > np.pi); ANGx[INDx] = ANGx[INDx] - 2 * np.pi | |
INDx = np.nonzero(ANGx > np.pi); ANGx[INDx] = ANGx[INDx] - 2 * np.pi | |
Px = (ANGx + np.pi) / (2*np.pi) * sphereW + 0.5 | |
Py = ((-ANGy) + np.pi/2) / np.pi * sphereH + 0.5 | |
INDxx = np.nonzero(Px < 1) | |
Px[INDxx] = Px[INDxx] + sphereW | |
im = np.concatenate([im, im[:, :2]], 1) | |
Px = Px.reshape(new_imgH, new_imgH, order='F') | |
Py = Py.reshape(new_imgH, new_imgH, order='F') | |
warped_im = warpImageFast(im, Px, Py) | |
return warped_im | |
def separatePano(panoImg, fov, x, y, imgSize=320): | |
'''cut a panorama image into several separate views''' | |
assert x.shape == y.shape | |
if not isinstance(fov, np.ndarray): | |
fov = fov * np.ones_like(x) | |
sepScene = [ | |
{ | |
'img': imgLookAt(panoImg.copy(), xi, yi, imgSize, fovi), | |
'vx': xi, | |
'vy': yi, | |
'fov': fovi, | |
'sz': imgSize, | |
} | |
for xi, yi, fovi in zip(x, y, fov) | |
] | |
return sepScene | |
def lsdWrap(img): | |
''' | |
Opencv implementation of | |
Rafael Grompone von Gioi, Jérémie Jakubowicz, Jean-Michel Morel, and Gregory Randall, | |
LSD: a Line Segment Detector, Image Processing On Line, vol. 2012. | |
[Rafael12] http://www.ipol.im/pub/art/2012/gjmr-lsd/?utm_source=doi | |
@img | |
input image | |
''' | |
if len(img.shape) == 3: | |
img = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY) | |
lines = lsd(img, quant=0.7) | |
if lines is None: | |
return np.zeros_like(img), np.array([]) | |
edgeMap = np.zeros_like(img) | |
for i in range(lines.shape[0]): | |
pt1 = (int(lines[i, 0]), int(lines[i, 1])) | |
pt2 = (int(lines[i, 2]), int(lines[i, 3])) | |
width = lines[i, 4] | |
cv2.line(edgeMap, pt1, pt2, 255, int(np.ceil(width / 2))) | |
edgeList = np.concatenate([lines, np.ones_like(lines[:, :2])], 1) | |
return edgeMap, edgeList | |
def edgeFromImg2Pano(edge): | |
edgeList = edge['edgeLst'] | |
if len(edgeList) == 0: | |
return np.array([]) | |
vx = edge['vx'] | |
vy = edge['vy'] | |
fov = edge['fov'] | |
imH, imW = edge['img'].shape | |
R = (imW/2) / np.tan(fov/2) | |
# im is the tangent plane, contacting with ball at [x0 y0 z0] | |
x0 = R * np.cos(vy) * np.sin(vx) | |
y0 = R * np.cos(vy) * np.cos(vx) | |
z0 = R * np.sin(vy) | |
vecposX = np.array([np.cos(vx), -np.sin(vx), 0]) | |
vecposY = np.cross(np.array([x0, y0, z0]), vecposX) | |
vecposY = vecposY / np.sqrt(vecposY @ vecposY.T) | |
vecposX = vecposX.reshape(1, -1) | |
vecposY = vecposY.reshape(1, -1) | |
Xc = (0 + imW-1) / 2 | |
Yc = (0 + imH-1) / 2 | |
vecx1 = edgeList[:, [0]] - Xc | |
vecy1 = edgeList[:, [1]] - Yc | |
vecx2 = edgeList[:, [2]] - Xc | |
vecy2 = edgeList[:, [3]] - Yc | |
vec1 = np.tile(vecx1, [1, 3]) * vecposX + np.tile(vecy1, [1, 3]) * vecposY | |
vec2 = np.tile(vecx2, [1, 3]) * vecposX + np.tile(vecy2, [1, 3]) * vecposY | |
coord1 = [[x0, y0, z0]] + vec1 | |
coord2 = [[x0, y0, z0]] + vec2 | |
normal = np.cross(coord1, coord2, axis=1) | |
normal = normal / np.linalg.norm(normal, axis=1, keepdims=True) | |
panoList = np.hstack([normal, coord1, coord2, edgeList[:, [-1]]]) | |
return panoList | |
def _intersection(range1, range2): | |
if range1[1] < range1[0]: | |
range11 = [range1[0], 1] | |
range12 = [0, range1[1]] | |
else: | |
range11 = range1 | |
range12 = [0, 0] | |
if range2[1] < range2[0]: | |
range21 = [range2[0], 1] | |
range22 = [0, range2[1]] | |
else: | |
range21 = range2 | |
range22 = [0, 0] | |
b = max(range11[0], range21[0]) < min(range11[1], range21[1]) | |
if b: | |
return b | |
b2 = max(range12[0], range22[0]) < min(range12[1], range22[1]) | |
b = b or b2 | |
return b | |
def _insideRange(pt, range): | |
if range[1] > range[0]: | |
b = pt >= range[0] and pt <= range[1] | |
else: | |
b1 = pt >= range[0] and pt <= 1 | |
b2 = pt >= 0 and pt <= range[1] | |
b = b1 or b2 | |
return b | |
def combineEdgesN(edges): | |
''' | |
Combine some small line segments, should be very conservative | |
OUTPUT | |
lines: combined line segments | |
ori_lines: original line segments | |
line format [nx ny nz projectPlaneID umin umax LSfov score] | |
''' | |
arcList = [] | |
for edge in edges: | |
panoLst = edge['panoLst'] | |
if len(panoLst) == 0: | |
continue | |
arcList.append(panoLst) | |
arcList = np.vstack(arcList) | |
# ori lines | |
numLine = len(arcList) | |
ori_lines = np.zeros((numLine, 8)) | |
areaXY = np.abs(arcList[:, 2]) | |
areaYZ = np.abs(arcList[:, 0]) | |
areaZX = np.abs(arcList[:, 1]) | |
planeIDs = np.argmax(np.stack([areaXY, areaYZ, areaZX], -1), 1) + 1 # XY YZ ZX | |
for i in range(numLine): | |
ori_lines[i, :3] = arcList[i, :3] | |
ori_lines[i, 3] = planeIDs[i] | |
coord1 = arcList[i, 3:6] | |
coord2 = arcList[i, 6:9] | |
uv = xyz2uvN(np.stack([coord1, coord2]), planeIDs[i]) | |
umax = uv[:, 0].max() + np.pi | |
umin = uv[:, 0].min() + np.pi | |
if umax - umin > np.pi: | |
ori_lines[i, 4:6] = np.array([umax, umin]) / 2 / np.pi | |
else: | |
ori_lines[i, 4:6] = np.array([umin, umax]) / 2 / np.pi | |
ori_lines[i, 6] = np.arccos(( | |
np.dot(coord1, coord2) / (np.linalg.norm(coord1) * np.linalg.norm(coord2)) | |
).clip(-1, 1)) | |
ori_lines[i, 7] = arcList[i, 9] | |
# additive combination | |
lines = ori_lines.copy() | |
for _ in range(3): | |
numLine = len(lines) | |
valid_line = np.ones(numLine, bool) | |
for i in range(numLine): | |
if not valid_line[i]: | |
continue | |
dotProd = (lines[:, :3] * lines[[i], :3]).sum(1) | |
valid_curr = np.logical_and((np.abs(dotProd) > np.cos(np.pi / 180)), valid_line) | |
valid_curr[i] = False | |
for j in np.nonzero(valid_curr)[0]: | |
range1 = lines[i, 4:6] | |
range2 = lines[j, 4:6] | |
valid_rag = _intersection(range1, range2) | |
if not valid_rag: | |
continue | |
# combine | |
I = np.argmax(np.abs(lines[i, :3])) | |
if lines[i, I] * lines[j, I] > 0: | |
nc = lines[i, :3] * lines[i, 6] + lines[j, :3] * lines[j, 6] | |
else: | |
nc = lines[i, :3] * lines[i, 6] - lines[j, :3] * lines[j, 6] | |
nc = nc / np.linalg.norm(nc) | |
if _insideRange(range1[0], range2): | |
nrmin = range2[0] | |
else: | |
nrmin = range1[0] | |
if _insideRange(range1[1], range2): | |
nrmax = range2[1] | |
else: | |
nrmax = range1[1] | |
u = np.array([[nrmin], [nrmax]]) * 2 * np.pi - np.pi | |
v = computeUVN(nc, u, lines[i, 3]) | |
xyz = uv2xyzN(np.hstack([u, v]), lines[i, 3]) | |
l = np.arccos(np.dot(xyz[0, :], xyz[1, :]).clip(-1, 1)) | |
scr = (lines[i,6]*lines[i,7] + lines[j,6]*lines[j,7]) / (lines[i,6]+lines[j,6]) | |
lines[i] = [*nc, lines[i, 3], nrmin, nrmax, l, scr] | |
valid_line[j] = False | |
lines = lines[valid_line] | |
return lines, ori_lines | |
def icosahedron2sphere(level): | |
# this function use a icosahedron to sample uniformly on a sphere | |
a = 2 / (1 + np.sqrt(5)) | |
M = np.array([ | |
0, a, -1, a, 1, 0, -a, 1, 0, | |
0, a, 1, -a, 1, 0, a, 1, 0, | |
0, a, 1, 0, -a, 1, -1, 0, a, | |
0, a, 1, 1, 0, a, 0, -a, 1, | |
0, a, -1, 0, -a, -1, 1, 0, -a, | |
0, a, -1, -1, 0, -a, 0, -a, -1, | |
0, -a, 1, a, -1, 0, -a, -1, 0, | |
0, -a, -1, -a, -1, 0, a, -1, 0, | |
-a, 1, 0, -1, 0, a, -1, 0, -a, | |
-a, -1, 0, -1, 0, -a, -1, 0, a, | |
a, 1, 0, 1, 0, -a, 1, 0, a, | |
a, -1, 0, 1, 0, a, 1, 0, -a, | |
0, a, 1, -1, 0, a, -a, 1, 0, | |
0, a, 1, a, 1, 0, 1, 0, a, | |
0, a, -1, -a, 1, 0, -1, 0, -a, | |
0, a, -1, 1, 0, -a, a, 1, 0, | |
0, -a, -1, -1, 0, -a, -a, -1, 0, | |
0, -a, -1, a, -1, 0, 1, 0, -a, | |
0, -a, 1, -a, -1, 0, -1, 0, a, | |
0, -a, 1, 1, 0, a, a, -1, 0]) | |
coor = M.T.reshape(3, 60, order='F').T | |
coor, idx = np.unique(coor, return_inverse=True, axis=0) | |
tri = idx.reshape(3, 20, order='F').T | |
# extrude | |
coor = list(coor / np.tile(np.linalg.norm(coor, axis=1, keepdims=True), (1, 3))) | |
for _ in range(level): | |
triN = [] | |
for t in range(len(tri)): | |
n = len(coor) | |
coor.append((coor[tri[t, 0]] + coor[tri[t, 1]]) / 2) | |
coor.append((coor[tri[t, 1]] + coor[tri[t, 2]]) / 2) | |
coor.append((coor[tri[t, 2]] + coor[tri[t, 0]]) / 2) | |
triN.append([n, tri[t, 0], n+2]) | |
triN.append([n, tri[t, 1], n+1]) | |
triN.append([n+1, tri[t, 2], n+2]) | |
triN.append([n, n+1, n+2]) | |
tri = np.array(triN) | |
# uniquefy | |
coor, idx = np.unique(coor, return_inverse=True, axis=0) | |
tri = idx[tri] | |
# extrude | |
coor = list(coor / np.tile(np.sqrt(np.sum(coor * coor, 1, keepdims=True)), (1, 3))) | |
return np.array(coor), np.array(tri) | |
def curveFitting(inputXYZ, weight): | |
''' | |
@inputXYZ: N x 3 | |
@weight : N x 1 | |
''' | |
l = np.linalg.norm(inputXYZ, axis=1, keepdims=True) | |
inputXYZ = inputXYZ / l | |
weightXYZ = inputXYZ * weight | |
XX = np.sum(weightXYZ[:, 0] ** 2) | |
YY = np.sum(weightXYZ[:, 1] ** 2) | |
ZZ = np.sum(weightXYZ[:, 2] ** 2) | |
XY = np.sum(weightXYZ[:, 0] * weightXYZ[:, 1]) | |
YZ = np.sum(weightXYZ[:, 1] * weightXYZ[:, 2]) | |
ZX = np.sum(weightXYZ[:, 2] * weightXYZ[:, 0]) | |
A = np.array([ | |
[XX, XY, ZX], | |
[XY, YY, YZ], | |
[ZX, YZ, ZZ]]) | |
U, S, Vh = np.linalg.svd(A) | |
outputNM = Vh[-1, :] | |
outputNM = outputNM / np.linalg.norm(outputNM) | |
return outputNM | |
def sphereHoughVote(segNormal, segLength, segScores, binRadius, orthTolerance, candiSet, force_unempty=True): | |
# initial guess | |
numLinesg = len(segNormal) | |
voteBinPoints = candiSet.copy() | |
voteBinPoints = voteBinPoints[~(voteBinPoints[:,2] < 0)] | |
reversValid = (segNormal[:, 2] < 0).reshape(-1) | |
segNormal[reversValid] = -segNormal[reversValid] | |
voteBinUV = xyz2uvN(voteBinPoints) | |
numVoteBin = len(voteBinPoints) | |
voteBinValues = np.zeros(numVoteBin) | |
for i in range(numLinesg): | |
tempNorm = segNormal[[i]] | |
tempDots = (voteBinPoints * tempNorm).sum(1) | |
valid = np.abs(tempDots) < np.cos((90 - binRadius) * np.pi / 180) | |
voteBinValues[valid] = voteBinValues[valid] + segScores[i] * segLength[i] | |
checkIDs1 = np.nonzero(voteBinUV[:, [1]] > np.pi / 3)[0] | |
voteMax = 0 | |
checkID1Max = 0 | |
checkID2Max = 0 | |
checkID3Max = 0 | |
for j in range(len(checkIDs1)): | |
checkID1 = checkIDs1[j] | |
vote1 = voteBinValues[checkID1] | |
if voteBinValues[checkID1] == 0 and force_unempty: | |
continue | |
checkNormal = voteBinPoints[[checkID1]] | |
dotProduct = (voteBinPoints * checkNormal).sum(1) | |
checkIDs2 = np.nonzero(np.abs(dotProduct) < np.cos((90 - orthTolerance) * np.pi / 180))[0] | |
for i in range(len(checkIDs2)): | |
checkID2 = checkIDs2[i] | |
if voteBinValues[checkID2] == 0 and force_unempty: | |
continue | |
vote2 = vote1 + voteBinValues[checkID2] | |
cpv = np.cross(voteBinPoints[checkID1], voteBinPoints[checkID2]).reshape(1, 3) | |
cpn = np.linalg.norm(cpv) | |
dotProduct = (voteBinPoints * cpv).sum(1) / cpn | |
checkIDs3 = np.nonzero(np.abs(dotProduct) > np.cos(orthTolerance * np.pi / 180))[0] | |
for k in range(len(checkIDs3)): | |
checkID3 = checkIDs3[k] | |
if voteBinValues[checkID3] == 0 and force_unempty: | |
continue | |
vote3 = vote2 + voteBinValues[checkID3] | |
if vote3 > voteMax: | |
lastStepCost = vote3 - voteMax | |
if voteMax != 0: | |
tmp = (voteBinPoints[[checkID1Max, checkID2Max, checkID3Max]] * \ | |
voteBinPoints[[checkID1, checkID2, checkID3]]).sum(1) | |
lastStepAngle = np.arccos(tmp.clip(-1, 1)) | |
else: | |
lastStepAngle = np.zeros(3) | |
checkID1Max = checkID1 | |
checkID2Max = checkID2 | |
checkID3Max = checkID3 | |
voteMax = vote3 | |
if checkID1Max == 0: | |
print('[WARN] sphereHoughVote: no orthogonal voting exist', file=sys.stderr) | |
return None, 0, 0 | |
initXYZ = voteBinPoints[[checkID1Max, checkID2Max, checkID3Max]] | |
# refine | |
refiXYZ = np.zeros((3, 3)) | |
dotprod = (segNormal * initXYZ[[0]]).sum(1) | |
valid = np.abs(dotprod) < np.cos((90 - binRadius) * np.pi / 180) | |
validNm = segNormal[valid] | |
validWt = segLength[valid] * segScores[valid] | |
validWt = validWt / validWt.max() | |
refiNM = curveFitting(validNm, validWt) | |
refiXYZ[0] = refiNM.copy() | |
dotprod = (segNormal * initXYZ[[1]]).sum(1) | |
valid = np.abs(dotprod) < np.cos((90 - binRadius) * np.pi / 180) | |
validNm = segNormal[valid] | |
validWt = segLength[valid] * segScores[valid] | |
validWt = validWt / validWt.max() | |
validNm = np.vstack([validNm, refiXYZ[[0]]]) | |
validWt = np.vstack([validWt, validWt.sum(0, keepdims=1) * 0.1]) | |
refiNM = curveFitting(validNm, validWt) | |
refiXYZ[1] = refiNM.copy() | |
refiNM = np.cross(refiXYZ[0], refiXYZ[1]) | |
refiXYZ[2] = refiNM / np.linalg.norm(refiNM) | |
return refiXYZ, lastStepCost, lastStepAngle | |
def findMainDirectionEMA(lines): | |
'''compute vp from set of lines''' | |
# initial guess | |
segNormal = lines[:, :3] | |
segLength = lines[:, [6]] | |
segScores = np.ones((len(lines), 1)) | |
shortSegValid = (segLength < 5 * np.pi / 180).reshape(-1) | |
segNormal = segNormal[~shortSegValid, :] | |
segLength = segLength[~shortSegValid] | |
segScores = segScores[~shortSegValid] | |
numLinesg = len(segNormal) | |
candiSet, tri = icosahedron2sphere(3) | |
ang = np.arccos((candiSet[tri[0,0]] * candiSet[tri[0,1]]).sum().clip(-1, 1)) / np.pi * 180 | |
binRadius = ang / 2 | |
initXYZ, score, angle = sphereHoughVote(segNormal, segLength, segScores, 2*binRadius, 2, candiSet) | |
if initXYZ is None: | |
print('[WARN] findMainDirectionEMA: initial failed', file=sys.stderr) | |
return None, score, angle | |
# iterative refine | |
iter_max = 3 | |
candiSet, tri = icosahedron2sphere(5) | |
numCandi = len(candiSet) | |
angD = np.arccos((candiSet[tri[0, 0]] * candiSet[tri[0, 1]]).sum().clip(-1, 1)) / np.pi * 180 | |
binRadiusD = angD / 2 | |
curXYZ = initXYZ.copy() | |
tol = np.linspace(4*binRadius, 4*binRadiusD, iter_max) # shrink down ls and candi | |
for it in range(iter_max): | |
dot1 = np.abs((segNormal * curXYZ[[0]]).sum(1)) | |
dot2 = np.abs((segNormal * curXYZ[[1]]).sum(1)) | |
dot3 = np.abs((segNormal * curXYZ[[2]]).sum(1)) | |
valid1 = dot1 < np.cos((90 - tol[it]) * np.pi / 180) | |
valid2 = dot2 < np.cos((90 - tol[it]) * np.pi / 180) | |
valid3 = dot3 < np.cos((90 - tol[it]) * np.pi / 180) | |
valid = valid1 | valid2 | valid3 | |
if np.sum(valid) == 0: | |
print('[WARN] findMainDirectionEMA: zero line segments for voting', file=sys.stderr) | |
break | |
subSegNormal = segNormal[valid] | |
subSegLength = segLength[valid] | |
subSegScores = segScores[valid] | |
dot1 = np.abs((candiSet * curXYZ[[0]]).sum(1)) | |
dot2 = np.abs((candiSet * curXYZ[[1]]).sum(1)) | |
dot3 = np.abs((candiSet * curXYZ[[2]]).sum(1)) | |
valid1 = dot1 > np.cos(tol[it] * np.pi / 180) | |
valid2 = dot2 > np.cos(tol[it] * np.pi / 180) | |
valid3 = dot3 > np.cos(tol[it] * np.pi / 180) | |
valid = valid1 | valid2 | valid3 | |
if np.sum(valid) == 0: | |
print('[WARN] findMainDirectionEMA: zero line segments for voting', file=sys.stderr) | |
break | |
subCandiSet = candiSet[valid] | |
tcurXYZ, _, _ = sphereHoughVote(subSegNormal, subSegLength, subSegScores, 2*binRadiusD, 2, subCandiSet) | |
if tcurXYZ is None: | |
print('[WARN] findMainDirectionEMA: no answer found', file=sys.stderr) | |
break | |
curXYZ = tcurXYZ.copy() | |
mainDirect = curXYZ.copy() | |
mainDirect[0] = mainDirect[0] * np.sign(mainDirect[0,2]) | |
mainDirect[1] = mainDirect[1] * np.sign(mainDirect[1,2]) | |
mainDirect[2] = mainDirect[2] * np.sign(mainDirect[2,2]) | |
uv = xyz2uvN(mainDirect) | |
I1 = np.argmax(uv[:,1]) | |
J = np.setdiff1d(np.arange(3), I1) | |
I2 = np.argmin(np.abs(np.sin(uv[J,0]))) | |
I2 = J[I2] | |
I3 = np.setdiff1d(np.arange(3), np.hstack([I1, I2])) | |
mainDirect = np.vstack([mainDirect[I1], mainDirect[I2], mainDirect[I3]]) | |
mainDirect[0] = mainDirect[0] * np.sign(mainDirect[0,2]) | |
mainDirect[1] = mainDirect[1] * np.sign(mainDirect[1,1]) | |
mainDirect[2] = mainDirect[2] * np.sign(mainDirect[2,0]) | |
mainDirect = np.vstack([mainDirect, -mainDirect]) | |
return mainDirect, score, angle | |
def multi_linspace(start, stop, num): | |
div = (num - 1) | |
y = np.arange(0, num, dtype=np.float64) | |
steps = (stop - start) / div | |
return steps.reshape(-1, 1) * y + start.reshape(-1, 1) | |
def assignVanishingType(lines, vp, tol, area=10): | |
numLine = len(lines) | |
numVP = len(vp) | |
typeCost = np.zeros((numLine, numVP)) | |
# perpendicular | |
for vid in range(numVP): | |
cosint = (lines[:, :3] * vp[[vid]]).sum(1) | |
typeCost[:, vid] = np.arcsin(np.abs(cosint).clip(-1, 1)) | |
# infinity | |
u = np.stack([lines[:, 4], lines[:, 5]], -1) | |
u = u.reshape(-1, 1) * 2 * np.pi - np.pi | |
v = computeUVN_vec(lines[:, :3], u, lines[:, 3]) | |
xyz = uv2xyzN_vec(np.hstack([u, v]), np.repeat(lines[:, 3], 2)) | |
xyz = multi_linspace(xyz[0::2].reshape(-1), xyz[1::2].reshape(-1), 100) | |
xyz = np.vstack([blk.T for blk in np.split(xyz, numLine)]) | |
xyz = xyz / np.linalg.norm(xyz, axis=1, keepdims=True) | |
for vid in range(numVP): | |
ang = np.arccos(np.abs((xyz * vp[[vid]]).sum(1)).clip(-1, 1)) | |
notok = (ang < area * np.pi / 180).reshape(numLine, 100).sum(1) != 0 | |
typeCost[notok, vid] = 100 | |
I = typeCost.min(1) | |
tp = typeCost.argmin(1) | |
tp[I > tol] = numVP + 1 | |
return tp, typeCost | |
def refitLineSegmentB(lines, vp, vpweight=0.1): | |
''' | |
Refit direction of line segments | |
INPUT: | |
lines: original line segments | |
vp: vannishing point | |
vpweight: if set to 0, lines will not change; if set to inf, lines will | |
be forced to pass vp | |
''' | |
numSample = 100 | |
numLine = len(lines) | |
xyz = np.zeros((numSample+1, 3)) | |
wei = np.ones((numSample+1, 1)) | |
wei[numSample] = vpweight * numSample | |
lines_ali = lines.copy() | |
for i in range(numLine): | |
n = lines[i, :3] | |
sid = lines[i, 4] * 2 * np.pi | |
eid = lines[i, 5] * 2 * np.pi | |
if eid < sid: | |
x = np.linspace(sid, eid + 2 * np.pi, numSample) % (2 * np.pi) | |
else: | |
x = np.linspace(sid, eid, numSample) | |
u = -np.pi + x.reshape(-1, 1) | |
v = computeUVN(n, u, lines[i, 3]) | |
xyz[:numSample] = uv2xyzN(np.hstack([u, v]), lines[i, 3]) | |
xyz[numSample] = vp | |
outputNM = curveFitting(xyz, wei) | |
lines_ali[i, :3] = outputNM | |
return lines_ali | |
def paintParameterLine(parameterLine, width, height): | |
lines = parameterLine.copy() | |
panoEdgeC = np.zeros((height, width)) | |
num_sample = max(height, width) | |
for i in range(len(lines)): | |
n = lines[i, :3] | |
sid = lines[i, 4] * 2 * np.pi | |
eid = lines[i, 5] * 2 * np.pi | |
if eid < sid: | |
x = np.linspace(sid, eid + 2 * np.pi, num_sample) | |
x = x % (2 * np.pi) | |
else: | |
x = np.linspace(sid, eid, num_sample) | |
u = -np.pi + x.reshape(-1, 1) | |
v = computeUVN(n, u, lines[i, 3]) | |
xyz = uv2xyzN(np.hstack([u, v]), lines[i, 3]) | |
uv = xyz2uvN(xyz, 1) | |
m = np.minimum(np.floor((uv[:,0] + np.pi) / (2 * np.pi) * width) + 1, | |
width).astype(np.int32) | |
n = np.minimum(np.floor(((np.pi / 2) - uv[:, 1]) / np.pi * height) + 1, | |
height).astype(np.int32) | |
panoEdgeC[n-1, m-1] = i | |
return panoEdgeC | |
def panoEdgeDetection(img, viewSize=320, qError=0.7, refineIter=3): | |
''' | |
line detection on panorama | |
INPUT: | |
img: image waiting for detection, double type, range 0~1 | |
viewSize: image size of croped views | |
qError: set smaller if more line segment wanted | |
OUTPUT: | |
oLines: detected line segments | |
vp: vanishing point | |
views: separate views of panorama | |
edges: original detection of line segments in separate views | |
panoEdge: image for visualize line segments | |
''' | |
cutSize = viewSize | |
fov = np.pi / 3 | |
xh = np.arange(-np.pi, np.pi*5/6, np.pi/6) | |
yh = np.zeros(xh.shape[0]) | |
xp = np.array([-3/3, -2/3, -1/3, 0/3, 1/3, 2/3, -3/3, -2/3, -1/3, 0/3, 1/3, 2/3]) * np.pi | |
yp = np.array([ 1/4, 1/4, 1/4, 1/4, 1/4, 1/4, -1/4, -1/4, -1/4, -1/4, -1/4, -1/4]) * np.pi | |
x = np.concatenate([xh, xp, [0, 0]]) | |
y = np.concatenate([yh, yp, [np.pi/2., -np.pi/2]]) | |
sepScene = separatePano(img.copy(), fov, x, y, cutSize) | |
edge = [] | |
for i, scene in enumerate(sepScene): | |
edgeMap, edgeList = lsdWrap(scene['img']) | |
edge.append({ | |
'img': edgeMap, | |
'edgeLst': edgeList, | |
'vx': scene['vx'], | |
'vy': scene['vy'], | |
'fov': scene['fov'], | |
}) | |
edge[-1]['panoLst'] = edgeFromImg2Pano(edge[-1]) | |
lines, olines = combineEdgesN(edge) | |
clines = lines.copy() | |
for _ in range(refineIter): | |
mainDirect, score, angle = findMainDirectionEMA(clines) | |
tp, typeCost = assignVanishingType(lines, mainDirect[:3], 0.1, 10) | |
lines1 = lines[tp==0] | |
lines2 = lines[tp==1] | |
lines3 = lines[tp==2] | |
lines1rB = refitLineSegmentB(lines1, mainDirect[0], 0) | |
lines2rB = refitLineSegmentB(lines2, mainDirect[1], 0) | |
lines3rB = refitLineSegmentB(lines3, mainDirect[2], 0) | |
clines = np.vstack([lines1rB, lines2rB, lines3rB]) | |
panoEdge1r = paintParameterLine(lines1rB, img.shape[1], img.shape[0]) | |
panoEdge2r = paintParameterLine(lines2rB, img.shape[1], img.shape[0]) | |
panoEdge3r = paintParameterLine(lines3rB, img.shape[1], img.shape[0]) | |
panoEdger = np.stack([panoEdge1r, panoEdge2r, panoEdge3r], -1) | |
# output | |
olines = clines | |
vp = mainDirect | |
views = sepScene | |
edges = edge | |
panoEdge = panoEdger | |
return olines, vp, views, edges, panoEdge, score, angle | |
if __name__ == '__main__': | |
# disable OpenCV3's non thread safe OpenCL option | |
cv2.ocl.setUseOpenCL(False) | |
import os | |
import argparse | |
import PIL | |
from PIL import Image | |
import time | |
parser = argparse.ArgumentParser() | |
parser.add_argument('--i', required=True) | |
parser.add_argument('--o_prefix', required=True) | |
parser.add_argument('--qError', default=0.7, type=float) | |
parser.add_argument('--refineIter', default=3, type=int) | |
args = parser.parse_args() | |
# Read image | |
img_ori = np.array(Image.open(args.i).resize((1024, 512))) | |
# Vanishing point estimation & Line segments detection | |
s_time = time.time() | |
olines, vp, views, edges, panoEdge, score, angle = panoEdgeDetection(img_ori, | |
qError=args.qError, | |
refineIter=args.refineIter) | |
print('Elapsed time: %.2f' % (time.time() - s_time)) | |
panoEdge = (panoEdge > 0) | |
print('Vanishing point:') | |
for v in vp[2::-1]: | |
print('%.6f %.6f %.6f' % tuple(v)) | |
# Visualization | |
edg = rotatePanorama(panoEdge.astype(np.float64), vp[2::-1]) | |
img = rotatePanorama(img_ori / 255.0, vp[2::-1]) | |
one = img.copy() * 0.5 | |
one[(edg > 0.5).sum(-1) > 0] = 0 | |
one[edg[..., 0] > 0.5, 0] = 1 | |
one[edg[..., 1] > 0.5, 1] = 1 | |
one[edg[..., 2] > 0.5, 2] = 1 | |
Image.fromarray((edg * 255).astype(np.uint8)).save('%s_edg.png' % args.o_prefix) | |
Image.fromarray((img * 255).astype(np.uint8)).save('%s_img.png' % args.o_prefix) | |
Image.fromarray((one * 255).astype(np.uint8)).save('%s_one.png' % args.o_prefix) | |