OpenCV: Remap in reverse order - python

I want to project an image from spherical to cubemap. From what I understood studying maths, I need to create a theta, phi distribution for each pixel and then convert it into cartesian system to get a normalized pixel map.
I used the following code to do so
theta = 0
phi = np.pi/2
squareLength = 2048
# theta phi distribution for X-positive face
t = np.linspace(theta + np.pi/4, theta - np.pi/4, squareLength)
p = np.linspace(phi + np.pi/4, phi - np.pi/4, squareLength)
x, y = np.meshgrid(t, p)
# converting into cartesion sytem for X-positive face (where r is the distance from sphere center to cube plane and X is constantly 0.5 in cartesian system)
X = np.zeros_like(y)
X[:,:] = 0.5
r = X / (np.cos(x) * np.sin(y))
Y = r * np.sin(x) * np.sin(y)
Z = r * np.cos(y)
XYZ = np.stack((X, Y, Z), axis=2)
# shifting pixels from the negative side
XYZ = XYZ + [0, 0.5, 0.5]
# since i want to project on X-positive face my map should be
x_map = -XYZ[:, :, 1] * squareLength
y_map = XYZ[:,:, 2] * squareLength
The above map created should give me my desired result with cv2.remap() but it's not. Then I tried looping through pixels and implement my own remap without interpolation or extrapolation. With some hit and trial, I deduced the following formula which gives me the correct result
for i in range(2048):
for j in range(2048):
try:
image[int(y_map[i,j]), int(x_map[i,j])] = im[i, j]
except:
pass
which is reverse of actual cv2 remapping which says dst(x,y)=src(mapx(x,y),mapy(x,y))
I do not understand if did the math all wrong or is there a way to covert x_map and y_map to correct forms so that cv2.remap() gives the desired result.
INPUT IMAGE
DESIRED RESULT (this one is without interpolation using loops)
CURRENT RESULT (using cv2.remap())

I'm quite new in opencv and I didn't work with so difficult math algorithms before but I tried to do this. I rewrote your code a bit and here it is:
import numpy as np
import cv2
src = cv2.imread("data/pink_sq.png")
def make_map():
theta = 0
phi = np.pi / 2
squareLength = 4000
# theta phi distribution for X-positive face
t = np.linspace((theta - np.pi / 4), (theta + np.pi / 4), squareLength)
p = np.linspace((phi + np.pi / 4), (phi - np.pi / 4), squareLength)
x, y = np.meshgrid(t, p)
x_res = np.zeros_like(y)
x_res[:, :] = 0.5
r = x_res * (np.cos(x))
r /= np.amax(r)
y_res = r * x
z_res = r * np.cos(y)
xyz = np.stack((x_res, y_res, z_res), axis=2)
# shifting pixels from the negative side
xyz = xyz + [0, 0.5, 0.5]
# since i want to project on X-positive face my map should be
x_map = xyz[:, :, 1] * squareLength
y_map = xyz[:, :, 2] * squareLength
map_x = y_map.astype("float32")
map_y = x_map.astype("float32")
return map_x, map_y
map_x, map_y = make_map()
dst = cv2.remap(src, map_y, map_x, cv2.INTER_LINEAR)
cv2.imwrite("res.png", dst)
I don't understand the math in this code at all but I rewrote it a bit and I should say that it works quite good. Here is the result image:
And yes, there is a bit difference between my result picture and yours but I hope it is ok :) If I'm not right somewhere of course downvote this answer because I'm not sure that it is correct one.

I'm almost certain the issue has to do with the orientation of the reference frame in space. Maybe if you clarify the Math a bit we can help.

Related

How to smooth pyvista.StructuredGrid?

Here is a Hopf torus made in Python with PyVista:
import numpy as np
import pyvista as pv
A = 0.44
n = 3
def Gamma(t):
alpha = np.pi/2 - (np.pi/2-A)*np.cos(n*t)
beta = t + A*np.sin(2*n*t)
return np.array([
np.sin(alpha) * np.cos(beta),
np.sin(alpha) * np.sin(beta),
np.cos(alpha)
])
def HopfInverse(p, phi):
return np.array([
(1+p[2])*np.cos(phi),
p[0]*np.sin(phi) - p[1]*np.cos(phi),
p[0]*np.cos(phi) + p[1]*np.sin(phi),
(1+p[2])*np.sin(phi)
]) / np.sqrt(2*(1+p[2]))
def Stereo(q):
return 2*q[0:3] / (1-q[3])
def F(t, phi):
return Stereo(HopfInverse(Gamma(t), phi))
angle = np.linspace(0, 2 * np.pi, 300)
theta, phi = np.meshgrid(angle, angle)
x, y, z = F(theta, phi)
# Display the mesh
grid = pv.StructuredGrid(x, y, z)
grid.plot(smooth_shading=True)
The color is not entirely smooth: on the lobe at the bottom right, you can see a line which separates pale gray and dark gray. How to get rid of this line?
I think what's going on here is that there's no connectivity information where the two ends of your structured grid meet. One way to fix this is to turn your grid into a PolyData using the extract_geometry() method, and then using clean with a larger tolerance. This will force pyvista to realise that there's a seam in the mesh where points are doubled, causing the points to be merged and the seam closed:
import numpy as np
import pyvista as pv
A = 0.44
n = 3
def Gamma(t):
alpha = np.pi/2 - (np.pi/2-A)*np.cos(n*t)
beta = t + A*np.sin(2*n*t)
return np.array([
np.sin(alpha) * np.cos(beta),
np.sin(alpha) * np.sin(beta),
np.cos(alpha)
])
def HopfInverse(p, phi):
return np.array([
(1+p[2])*np.cos(phi),
p[0]*np.sin(phi) - p[1]*np.cos(phi),
p[0]*np.cos(phi) + p[1]*np.sin(phi),
(1+p[2])*np.sin(phi)
]) / np.sqrt(2*(1+p[2]))
def Stereo(q):
return 2*q[0:3] / (1-q[3])
def F(t, phi):
return Stereo(HopfInverse(Gamma(t), phi))
angle = np.linspace(0, 2 * np.pi, 300)
theta, phi = np.meshgrid(angle, angle)
x, y, z = F(theta, phi)
# Display the mesh, show seam
grid = pv.StructuredGrid(x, y, z)
grid.plot(smooth_shading=True)
# convert to PolyData and clean to remove the seam
cleaned_poly = grid.extract_geometry().clean(tolerance=1e-6)
cleaned_poly.plot(smooth_shading=True)
Your mileage for the tolerance parameter may vary.
Just as a piece of trivia, we can visualize the original seam by extracting the feature edges of your original grid:
grid.extract_feature_edges().plot()
These curves correspond to the open edges in your original grid:
>>> grid.extract_surface().n_open_edges
1196
Since your surface is closed and watertight, it should have 0 open edges:
>>> cleaned_poly.n_open_edges
0

How to apply a Gabor filter to an image with hexagonal sampling?

I want to use a Gabor filter as an interpolation method after the conversion of a square-sampled image to an hexagonally-sampled image.
Can I use the normal Gabor filter implementation in an hexagonally-sampled image? Or should I modify the code? If yes, then what part of the Gabor function should I modify for an hexagonally-sampled image?
I've tried implementing the algorithm but I just can't get it right.
Here's a code for Gabor filtering taken from GitHub.
import numpy as np
import cv2
# cv2.getGaborKernel(ksize, sigma, theta, lambda, gamma, psi, ktype)
# ksize - size of gabor filter (n, n)
# sigma - standard deviation of the gaussian function
# theta - orientation of the normal to the parallel stripes
# lambda - wavelength of the sunusoidal factor
# gamma - spatial aspect ratio
# psi - phase offset
# ktype - type and range of values that each pixel in the gabor kernel can hold
g_kernel = cv2.getGaborKernel((21, 21), 8.0, np.pi/4, 10.0, 0.5, 0, ktype=cv2.CV_32F)
img = cv2.imread('test.jpg')
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
filtered_img = cv2.filter2D(img, cv2.CV_8UC3, g_kernel)
cv2.imshow('image', img)
cv2.imshow('filtered image', filtered_img)
h, w = g_kernel.shape[:2]
g_kernel = cv2.resize(g_kernel, (3*w, 3*h), interpolation=cv2.INTER_CUBIC)
cv2.imshow('gabor kernel (resized)', g_kernel)
cv2.waitKey(0)
cv2.destroyAllWindows()
Assuming the ordinary data structure for a hexagonal grid, you can probably apply a 2d filter to a hexagonal-pixel image, but you need to create a filter that is evaluated at the appropriate coordinates. You see, a 2d filter is just a matrix of values generated by evaluating a function over xy coordinates in a grid. So, a 5x5 Gabor filter matrix is just a Gabor function evaluated at the xy coordinates shown here:
Pixels are "equally spaced" so we can simply pick the distance between each point in the grid to be 1 in x and 1 in y, and we get the Gabor function evaluated at the center of each pixel.
However, hexagonal pixels are not arranged this way. The centers of hexagonal pixels are arranged as thus:
Thus, in order to apply a filter to this, we need to evaluate the appropriate function at these points. Since the stock filters have been evaluated on a rectangular grid, we cannot use them (although they will produce something that probably looks reasonable).
Fortunately, the transformation is relatively easy. If we assume the vertical distance between two rows is 1, then the coordinates are almost just an np.arange.
import numpy as np
import matplotlib.pyplot as plt
ALTERNATE_ROW_SHIFT = 0+np.sqrt(3)/3 # every other row is "offset" by half a hexagon. If the sides are len 2/3, the shift is root 3 over 3
def hex_grid(rect_grid):
rect_grid = np.copy(rect_grid)
rect_grid[0,:,1::2] += ALTERNATE_ROW_SHIFT
return rect_grid
If you have access to the function that creates your filter, there will usually be some logic that creates a rectangular grid on which the function is subsequently evaluated. Drop the hex_grid function in on the line after to get hexagonally-spaced coordinates instead.
For example, the wikipedia page on Gabor filters has a python implementation to create a Gabor filter, shown here:
def gabor_fn(sigma, theta, Lambda, psi, gamma):
sigma_x = sigma
sigma_y = float(sigma) / gamma
# Bounding box
nstds = 3 # Number of standard deviation sigma
xmax = max(abs(nstds * sigma_x * np.cos(theta)), abs(nstds * sigma_y * np.sin(theta)))
xmax = np.ceil(max(1, xmax))
ymax = max(abs(nstds * sigma_x * np.sin(theta)), abs(nstds * sigma_y * np.cos(theta)))
ymax = np.ceil(max(1, ymax))
xmin = -xmax
ymin = -ymax
(y,x) = np.meshgrid(np.arange(ymin, ymax + 1), np.arange(xmin, xmax + 1))
# Rotation
x_theta = x * np.cos(theta) + y * np.sin(theta)
y_theta = -x * np.sin(theta) + y * np.cos(theta)
gb = np.exp(-.5 * (x_theta ** 2 / sigma_x ** 2 + y_theta ** 2 / sigma_y ** 2)) * np.cos(2 * np.pi / Lambda * x_theta + psi)
return gb
Note the line involving a np.meshgrid. This creates a rectagular grid with spacing 1 that is used on subsequent lines. We can simply transform those coordinates to create a new hex_gabor function (Note that this is 95% identical to the gabor_fn code):
def hex_gabor_fn(sigma, theta, Lambda, psi, gamma):
sigma_x = sigma
sigma_y = float(sigma) / gamma
# Bounding box
nstds = 3 # Number of standard deviation sigma
xmax = max(abs(nstds * sigma_x * np.cos(theta)), abs(nstds * sigma_y * np.sin(theta)))
xmax = np.ceil(max(1, xmax))
ymax = max(abs(nstds * sigma_x * np.sin(theta)), abs(nstds * sigma_y * np.cos(theta)))
ymax = np.ceil(max(1, ymax))
xmin = -xmax
ymin = -ymax
yx = np.meshgrid(np.arange(ymin, ymax + 1), np.arange(xmin, xmax + 1))
(y,x) = hex_grid(yx)
# Rotation
x_theta = x * np.cos(theta) + y * np.sin(theta)
y_theta = -x * np.sin(theta) + y * np.cos(theta)
gb = np.exp(-.5 * (x_theta ** 2 / sigma_x ** 2 + y_theta ** 2 / sigma_y ** 2)) * np.cos(2 * np.pi / Lambda * x_theta + psi)
return gb
if __name__ == "__main__":
g = gabor_fn(4,np.pi/4,4,0,2)
hg = hex_gabor_fn(4,np.pi/4,4,0,2)
plt.imshow(g)
plt.show()
plt.imshow(hg)
plt.show()
You should be able to drop the resulting kernel into this line cv2.filter2D(img, cv2.CV_8UC3, g_kernel).

Generate random locations within a triangular domain

I want to generate x and y having a uniform distribution and limited by [xmin,xmax] and [ymin,ymax]
The points (x,y) should be inside a triangle.
How can I solve such a problem?
Here's some code that generates points uniformly on an arbitrary triangle in the plane.
import random
def point_on_triangle(pt1, pt2, pt3):
"""
Random point on the triangle with vertices pt1, pt2 and pt3.
"""
x, y = sorted([random.random(), random.random()])
s, t, u = x, y - x, 1 - y
return (s * pt1[0] + t * pt2[0] + u * pt3[0],
s * pt1[1] + t * pt2[1] + u * pt3[1])
The idea is to compute a weighted average of the three vertices, with the weights given by a random break of the unit interval [0, 1] into three pieces (uniformly over all such breaks). Here x and y represent the places at which we break the unit interval, and s, t and u are the length of the pieces following that break. We then use s, t and u as the barycentric coordinates of the point in the triangle.
Here's a variant of the above that avoids the need to sort, instead making use of an absolute value call:
def point_on_triangle2(pt1, pt2, pt3):
"""
Random point on the triangle with vertices pt1, pt2 and pt3.
"""
x, y = random.random(), random.random()
q = abs(x - y)
s, t, u = q, 0.5 * (x + y - q), 1 - 0.5 * (q + x + y)
return (
s * pt1[0] + t * pt2[0] + u * pt3[0],
s * pt1[1] + t * pt2[1] + u * pt3[1],
)
Here's an example usage that generates 10000 points in a triangle:
pt1 = (1, 1)
pt2 = (2, 4)
pt3 = (5, 2)
points = [point_on_triangle(pt1, pt2, pt3) for _ in range(10000)]
And a plot obtained from the above, demonstrating the uniformity. The plot was generated by this code:
import matplotlib.pyplot as plt
x, y = zip(*points)
plt.scatter(x, y, s=0.1)
plt.show()
Here's the image:
And since you tagged the question with the "numpy" tag, here's a NumPy version that generates multiple samples at once. Note that it uses the matrix multiplication operator #, introduced in Python 3.5 and supported in NumPy >= 1.10. You'll need to replace that with a call to np.dot on older Python or NumPy versions.
import numpy as np
def points_on_triangle(v, n):
"""
Give n random points uniformly on a triangle.
The vertices of the triangle are given by the shape
(2, 3) array *v*: one vertex per row.
"""
x = np.sort(np.random.rand(2, n), axis=0)
return np.column_stack([x[0], x[1]-x[0], 1.0-x[1]]) # v
# Example usage
v = np.array([(1, 1), (2, 4), (5, 2)])
points = points_on_triangle(v, 10000)
Ok, time to add another version, I guess. There is known algorithm to sample uniformly in triangle, see paper, chapter 4.2 for details.
Python code:
import math
import random
import matplotlib.pyplot as plt
def trisample(A, B, C):
"""
Given three vertices A, B, C,
sample point uniformly in the triangle
"""
r1 = random.random()
r2 = random.random()
s1 = math.sqrt(r1)
x = A[0] * (1.0 - s1) + B[0] * (1.0 - r2) * s1 + C[0] * r2 * s1
y = A[1] * (1.0 - s1) + B[1] * (1.0 - r2) * s1 + C[1] * r2 * s1
return (x, y)
random.seed(312345)
A = (1, 1)
B = (2, 4)
C = (5, 2)
points = [trisample(A, B, C) for _ in range(10000)]
xx, yy = zip(*points)
plt.scatter(xx, yy, s=0.2)
plt.show()
And result looks like
Uniform on the triangle?
import numpy as np
N = 10 # number of points to create in one go
rvs = np.random.random((N, 2)) # uniform on the unit square
# Now use the fact that the unit square is tiled by the two triangles
# 0 <= y <= x <= 1 and 0 <= x < y <= 1
# which are mapped onto each other (except for the diagonal which has
# probability 0) by swapping x and y.
# We use this map to send all points of the square to the same of the
# two triangles. Because the map preserves areas this will yield
# uniformly distributed points.
rvs = np.where(rvs[:, 0, None]>rvs[:, 1, None], rvs, rvs[:, ::-1])
Finally, transform the coordinates
xmin, ymin, xmax, ymax = -0.1, 1.1, 2.0, 3.3
rvs = np.array((ymin, xmin)) + rvs*(ymax-ymin, xmax-xmin)
Uniform marginals? The simplest solution would be to uniformly concentrate the mass on the line (ymin, xmin) - (ymax, xmax)
rvs = np.random.random((N,))
rvs = np.c_[ymin + (ymax-ymin)*rvs, xmin + (xmax-xmin)*rvs]
but that is not very interesting, is it?

Orientation density plot (matplotlib)

I have a large set of vectors, which orientations I want to analyze by looking at the polar angles theta and phi. So I start with a 2D array of those angles.
I want to visualize it by plotting a histogram, or preferentially the kernel density onto a sphere (or the 2D projection, since I only need the show half a sphere).
If I use the polar angles for the histogram, it will be completely wrong, since - to give an example - the points theta = 1°, phi = 0° & 180° are closer than theta = 30°, phi = 0° & 180°. The binning fails.
If I use 2D cartesian coordinates of the projection for the histogram, I run into the same problem: points on x = 0, y = 0 & 0.1 are closer than x = 0, y = 0.5 & 0.6.
Right now I'm using a kernel density estimate with 3D cartesian coordinates:
from scipy import stats
X, Y, Z = [], [], []
for i in range(300):
for j in range(500):
X.append(np.sin(i / 300 * pi / 2) * np.cos(j / 500 * 2 * pi))
Y.append(np.sin(i / 300 * pi / 2) * np.sin(j / 500 * 2 * pi))
Z.append(np.cos(i / 300 * pi / 2))
X, Y, Z = np.array(X), np.array(Y), np.array(Z)
positions = np.vstack([X, Y, Z])
# x, y and z for my data: sin(theta) * cos(phi), sin(theta) * sin(phi), cos(theta)
values = np.vstack([np.sin(data[:, 1]) * np.cos(data[:, 2]), np.sin(data[:, 1]) * np.sin(data[:, 2]), np.cos(data[:, 1])])
fig, ax = subplots(1, figsize=(7, 7))
kernel = stats.gaussian_kde(values)
density = kernel(positions)
ax.tricontourf(X, Y, density, levels=np.linspace(0, max(density), 50))
plt.box('off')
ax.set_ylim((-1.01, 1.01))
ax.set_xlim((-1.01, 1.01))
This gives me:
Polar density plot
I don't know if a) this gives the correct result and b) if there isn't a better solution for this.

Inverse Filter for Gaussian Blur

I recently used a Gaussian convolution to blur an image. It works well, in fact it works excellently. However, now I'm trying to reverse it and my inverse filter keeps running into problems. The filter is supposed to run on the following principle:
G(x, y) / H(x, y) = F(x, y) where G(x, y) is the Fourier transform of the blurred image, H(x, y) is the Fourier transform of the blurring function and F(x, y) is the Fourier transform of the original image.
Currently, the resulting image looks exactly the same as the original. My algorithm is as follows:
from PIL import Image
import math
import cmath
import numpy as np
def reverseGaussianBlur(picture, r):
rs = int(math.ceil(r * 2.57) # Calculate significant radius
w, h = picture.size
pixels = list(picture.getdata()) # Image's pixels as list
fft_pixels = runFourier(pixels, False) # Run FFT
temp_picture = []
for u in range(0, h):
for v in range(0, w):
val = [0] * 3
wsum = 0
for iy in range(u - rs, u + rs + 1):
y = min(h - 1, max(0, iy))
for ix in range(v - rs, v + rs + 1):
x = min(w - 1, max(0, ix))
weight = (2 * math.pi) ** 0.5 * cmath.exp(-r * r * ((ix - v) *
(ix - v) + (iy - u) * (iy - u)) / 2)
if (weight.real > 1e-5):
val = [n + p / weight for n, p in zip(val, fft_pixels[y * w + x])]
wsum += weight
temp_picture.append(tuple([v * wsum for v in val]))
return_picture = [tuple(int(round(p)) for p in pixel) for pixel in
runFourier(temp_picture, True)] # Run Inverse FFT
return return_picture
Anyway, I'm not quite sure what's wrong and any help would be great.
When you blur an image, you're basically removing the high frequency components. They're gone. There is no reverse filter.
If you had applied a "filter" that took each pixel and replaced it with flat white, you wouldn't expect there to be a reverse filter for that, because all the details (except the size of the the original image) are lost. It's the same thing with blurring, only you've lost just the high frequency components.

Categories

Resources