I have the following Python program that endeavours to use the normalized iteration count algorithm to colour the Mandelbrot set:
from PIL import Image
import numpy as np
from matplotlib.colors import hsv_to_rgb
steps = 256 # maximum iterations
bailout_radius = 64 # bailout radius
def normalized_iteration(n, abs_z):
return n + 1 - np.log2(np.log(abs_z))/np.log(2)
def make_set(real_start, real_end, imag_start, imag_end, height):
width = \
int(abs(height * (real_end - real_start) / (imag_end - imag_start)))
real_axis = \
np.linspace(real_start, real_end, num = width)
imag_axis = \
np.linspace(imag_start, imag_end, num = height)
complex_plane = \
np.zeros((height, width), dtype = np.complex_)
real, imag = np.meshgrid(real_axis, imag_axis)
complex_plane.real = real
complex_plane.imag = imag
pixels = \
np.zeros((height, width, 3), dtype = np.float_)
new = np.zeros_like(complex_plane)
is_not_done = np.ones((height, width), dtype = bool)
# cosine_interpolation = lambda x: (np.cos(x * np.pi + np.pi) + 1) / 2
for i in range(steps):
new[is_not_done] = \
new[is_not_done] ** 2 + complex_plane[is_not_done]
mask = np.logical_and(np.absolute(new) > bailout_radius, is_not_done)
pixels[mask, :] = (i, 0.6, 1)
is_not_done = np.logical_and(is_not_done, np.logical_not(mask))
new_after_mask = np.zeros_like(complex_plane)
new_after_mask[np.logical_not(is_not_done)] = \
new[np.logical_not(is_not_done)]
new_after_mask[is_not_done] = bailout_radius
pixels[:, :, 0] = \
normalized_iteration(pixels[:, :, 0], np.absolute(new_after_mask)) / steps
image = Image.fromarray((hsv_to_rgb(np.flipud(pixels)) * 255).astype(np.uint8))
image.show()
make_set(-2, 1, -1, 1, 2000)
It produces a fairly nice image. However, when I compare it to other sets employing this algorithm, the colours in my set barely change. If I reduce steps, I get a more varied gradient, but that reduces the quality of the fractal. The important parts of this code are my normalized_iteration definition, which varies slightly from this Wikipedia article's version,
def normalized_iteration(n, abs_z):
return n + 1 - np.log2(np.log(abs_z))/np.log(2)
where I use that definition (mapping the function to the array of pixels),
pixels[:, :, 0] = \
normalized_iteration(pixels[:, :, 0], np.absolute(new_after_mask)) / steps
and the final array, where I convert the HSV format to RGB and turn the pixel values on [0, 1) to values on [0, 255)
image = Image.fromarray((hsv_to_rgb(np.flipud(pixels)) * 255).astype(np.uint8))
I have been fighting with this problem for a while now, and I am not sure of what is going wrong. Thanks for helping me determine how to make the gradient more varied in colour and for bearing with my perhaps hard-to-read code. Also, I realize that there is room for optimization in there.
Related
In other words, I want to make a heatmap (or surface plot) where the color varies as a function of 2 variables. (Specifically, luminance = magnitude and hue = phase.) Is there any native way to do this?
Some examples of similar plots:
Several good examples of exactly(?) what I want to do.
More examples from astronomy, but with non-perceptual hue
Edit: This is what I did with it: https://github.com/endolith/complex_colormap
imshow can take an array of [r, g, b] entries. So you can convert the absolute values to intensities and phases - to hues.
I will use as an example complex numbers, because for it it makes the most sense. If needed, you can always add numpy arrays Z = X + 1j * Y.
So for your data Z you can use e.g.
imshow(complex_array_to_rgb(Z))
where (EDIT: made it quicker and nicer thanks to this suggestion)
def complex_array_to_rgb(X, theme='dark', rmax=None):
'''Takes an array of complex number and converts it to an array of [r, g, b],
where phase gives hue and saturaton/value are given by the absolute value.
Especially for use with imshow for complex plots.'''
absmax = rmax or np.abs(X).max()
Y = np.zeros(X.shape + (3,), dtype='float')
Y[..., 0] = np.angle(X) / (2 * pi) % 1
if theme == 'light':
Y[..., 1] = np.clip(np.abs(X) / absmax, 0, 1)
Y[..., 2] = 1
elif theme == 'dark':
Y[..., 1] = 1
Y[..., 2] = np.clip(np.abs(X) / absmax, 0, 1)
Y = matplotlib.colors.hsv_to_rgb(Y)
return Y
So, for example:
Z = np.array([[3*(x + 1j*y)**3 + 1/(x + 1j*y)**2
for x in arange(-1,1,0.05)] for y in arange(-1,1,0.05)])
imshow(complex_array_to_rgb(Z, rmax=5), extent=(-1,1,-1,1))
imshow(complex_array_to_rgb(Z, rmax=5, theme='light'), extent=(-1,1,-1,1))
imshow will take an NxMx3 (rbg) or NxMx4 (grba) array so you can do your color mapping 'by hand'.
You might be able to get a bit of traction by sub-classing Normalize to map your vector to a scaler and laying out a custom color map very cleverly (but I think this will end up having to bin one of your dimensions).
I have done something like this (pdf link, see figure on page 24), but the code is in MATLAB (and buried someplace in my archives).
I agree a bi-variate color map would be useful (primarily for representing very dense vector fields where your kinda up the creek no matter what you do).
I think the obvious extension is to let color maps take complex arguments. It would require specialized sub-classes of Normalize and Colormap and I am going back and forth on if I think it would be a lot of work to implement. I suspect if you get it working by hand it will just be a matter of api wrangling.
I created an easy to use 2D colormap class, that takes 2 NumPy arrays and maps them to an RGB image, based on a reference image.
I used #GjjvdBurg's answer as a starting point. With a bit of work, this could still be improved, and possibly turned into a proper Python module - if you want, feel free to do so, I grant you all credits.
TL;DR:
# read reference image
cmap_2d = ColorMap2D('const_chroma.jpeg', reverse_x=True) # , xclip=(0,0.9))
# map the data x and y to the RGB space, defined by the image
rgb = cmap_2d(data_x, data_y)
# generate a colorbar image
cbar_rgb = cmap_2d.generate_cbar()
The ColorMap2D class:
class ColorMap2D:
def __init__(self, filename: str, transpose=False, reverse_x=False, reverse_y=False, xclip=None, yclip=None):
"""
Maps two 2D array to an RGB color space based on a given reference image.
Args:
filename (str): reference image to read the x-y colors from
rotate (bool): if True, transpose the reference image (swap x and y axes)
reverse_x (bool): if True, reverse the x scale on the reference
reverse_y (bool): if True, reverse the y scale on the reference
xclip (tuple): clip the image to this portion on the x scale; (0,1) is the whole image
yclip (tuple): clip the image to this portion on the y scale; (0,1) is the whole image
"""
self._colormap_file = filename or COLORMAP_FILE
self._img = plt.imread(self._colormap_file)
if transpose:
self._img = self._img.transpose()
if reverse_x:
self._img = self._img[::-1,:,:]
if reverse_y:
self._img = self._img[:,::-1,:]
if xclip is not None:
imin, imax = map(lambda x: int(self._img.shape[0] * x), xclip)
self._img = self._img[imin:imax,:,:]
if yclip is not None:
imin, imax = map(lambda x: int(self._img.shape[1] * x), yclip)
self._img = self._img[:,imin:imax,:]
if issubclass(self._img.dtype.type, np.integer):
self._img = self._img / 255.0
self._width = len(self._img)
self._height = len(self._img[0])
self._range_x = (0, 1)
self._range_y = (0, 1)
#staticmethod
def _scale_to_range(u: np.ndarray, u_min: float, u_max: float) -> np.ndarray:
return (u - u_min) / (u_max - u_min)
def _map_to_x(self, val: np.ndarray) -> np.ndarray:
xmin, xmax = self._range_x
val = self._scale_to_range(val, xmin, xmax)
rescaled = (val * (self._width - 1))
return rescaled.astype(int)
def _map_to_y(self, val: np.ndarray) -> np.ndarray:
ymin, ymax = self._range_y
val = self._scale_to_range(val, ymin, ymax)
rescaled = (val * (self._height - 1))
return rescaled.astype(int)
def __call__(self, val_x, val_y):
"""
Take val_x and val_y, and associate the RGB values
from the reference picture to each item. val_x and val_y
must have the same shape.
"""
if val_x.shape != val_y.shape:
raise ValueError(f'x and y array must have the same shape, but have {val_x.shape} and {val_y.shape}.')
self._range_x = (np.amin(val_x), np.amax(val_x))
self._range_y = (np.amin(val_y), np.amax(val_y))
x_indices = self._map_to_x(val_x)
y_indices = self._map_to_y(val_y)
i_xy = np.stack((x_indices, y_indices), axis=-1)
rgb = np.zeros((*val_x.shape, 3))
for indices in np.ndindex(val_x.shape):
img_indices = tuple(i_xy[indices])
rgb[indices] = self._img[img_indices]
return rgb
def generate_cbar(self, nx=100, ny=100):
"generate an image that can be used as a 2D colorbar"
x = np.linspace(0, 1, nx)
y = np.linspace(0, 1, ny)
return self.__call__(*np.meshgrid(x, y))
Usage:
Full example, using the constant chroma reference taken from here as a screenshot:
# generate data
x = y = np.linspace(-2, 2, 300)
xx, yy = np.meshgrid(x, y)
ampl = np.exp(-(xx ** 2 + yy ** 2))
phase = (xx ** 2 - yy ** 2) * 6 * np.pi
data = ampl * np.exp(1j * phase)
data_x, data_y = np.abs(data), np.angle(data)
# Here is the 2D colormap part
cmap_2d = ColorMap2D('const_chroma.jpeg', reverse_x=True) # , xclip=(0,0.9))
rgb = cmap_2d(data_x, data_y)
cbar_rgb = cmap_2d.generate_cbar()
# plot the data
fig, plot_ax = plt.subplots(figsize=(8, 6))
plot_extent = (x.min(), x.max(), y.min(), y.max())
plot_ax.imshow(rgb, aspect='auto', extent=plot_extent, origin='lower')
plot_ax.set_xlabel('x')
plot_ax.set_ylabel('y')
plot_ax.set_title('data')
# create a 2D colorbar and make it fancy
plt.subplots_adjust(left=0.1, right=0.65)
bar_ax = fig.add_axes([0.68, 0.15, 0.15, 0.3])
cmap_extent = (data_x.min(), data_x.max(), data_y.min(), data_y.max())
bar_ax.imshow(cbar_rgb, extent=cmap_extent, aspect='auto', origin='lower',)
bar_ax.set_xlabel('amplitude')
bar_ax.set_ylabel('phase')
bar_ax.yaxis.tick_right()
bar_ax.yaxis.set_label_position('right')
for item in ([bar_ax.title, bar_ax.xaxis.label, bar_ax.yaxis.label] +
bar_ax.get_xticklabels() + bar_ax.get_yticklabels()):
item.set_fontsize(7)
plt.show()
I know this is an old post, but want to help out others that may arrive late. Below is a python function to implement complex_to_rgb from sage. Note: This implementation isn't optimal, but it is readable. See links: (examples)(source code)
Code:
import numpy as np
def complex_to_rgb(z_values):
width = z_values.shape[0]
height = z_values.shape[1]
rgb = np.zeros(shape=(width, height, 3))
for i in range(width):
row = z_values[i]
for j in range(height):
# define value, real(value), imag(value)
zz = row[j]
x = np.real(zz)
y = np.imag(zz)
# define magnitued and argument
magnitude = np.hypot(x, y)
arg = np.arctan2(y, x)
# define lighness
lightness = np.arctan(np.log(np.sqrt(magnitude) + 1)) * (4 / np.pi) - 1
if lightness < 0:
bot = 0
top = 1 + lightness
else:
bot = lightness
top = 1
# define hue
hue = 3 * arg / np.pi
if hue < 0:
hue += 6
# set ihue and use it to define rgb values based on cases
ihue = int(hue)
# case 1
if ihue == 0:
r = top
g = bot + hue * (top - bot)
b = bot
# case 2
elif ihue == 1:
r = bot + (2 - hue) * (top - bot)
g = top
b = bot
# case 3
elif ihue == 2:
r = bot
g = top
b = bot + (hue - 2) * (top - bot)
# case 4
elif ihue == 3:
r = bot
g = bot + (4 - hue) * (top - bot)
b = top
# case 5
elif ihue == 4:
r = bot + (hue - 4) * (top - bot)
g = bot
b = top
# case 6
else:
r = top
g = bot
b = bot + (6 - hue) * (top - bot)
# set rgb array values
rgb[i, j, 0] = r
rgb[i, j, 1] = g
rgb[i, j, 2] = b
return rgb
I am trying to rotate the image for any given angle.
I am rotating with the center of the image as the origin.
But the code is not doing the rotation as expected.
I am attaching the code below.
import math
import numpy as np
import cv2
im = cv2.imread("Samples\\baboon.jpg", cv2.IMREAD_GRAYSCALE)
new = np.zeros(im.shape,np.uint8)
new_x = im.shape[0] // 2
new_y = im.shape[1] // 2
x = int(input("Enter the angle : "))
trans_mat = np.array([[math.cos(x), math.sin(x), 0],[-math.sin(x), math.cos(x), 0],[0, 0, 1]])
for i in range(-new_x, im.shape[0] - new_x):
for j in range(-new_y, im.shape[1] - new_y):
vec = np.matmul([i, j, 1], trans_mat)
if round(vec[0] + new_x) < 512 and round(vec[1] + new_y) < 512:
new[round(vec[0]+new_x), round(vec[1]+new_y)] = im[i+new_x,j+new_y]
cv2.imshow("rot",new)
cv2.imshow("1",im)
cv2.waitKey(0)
cv2.destroyAllWindows()
It looks like you are trying to implement a nearest-neighbor resampler. What you are doing is going through the image and mapping each input pixel to a new location in the output image. This can lead to problems like pixels overwriting each other incorrectly, output pixels being left empty, and similar.
I would suggest (based on experience) that you are looking at the problem backwards. Rather than looking at where an input pixel ends up in the output, you should consider where each output pixel originates in the input. That way, you have no ambiguity about nearest neighbors, and the entire image array will be filled.
You want to rotate about the center. The current rotation matrix you are using rotates about (0, 0). To compensate for that, you need to translate the center of the image to (0, 0), rotate, and then translate back. Rather than developing the full affine matrix, I will show you how to do the individual operations manually, and then how to combine them into the transform matrix.
Manual Computation
First get an input and output image:
im = cv2.imread("Samples\\baboon.jpg", cv2.IMREAD_GRAYSCALE)
new = np.zeros_like(im)
Then determine the center of rotation. Be clear about your dimensions x is usually the column (dim 1), not the row (dim 0):
center_row = im.shape[0] // 2
center_col = im.shape[1] // 2
Compute the radial coordinates of each pixel in the image, shaped to the corresponding dimension:
row_coord = np.arange(im.shape[0])[:, None] - center_row
col_coord = np.arange(im.shape[1]) - center_col
row_coord and col_coord are the distances from center in the output image. Now compute the locations where they came from in the input. Notice that we can use broadcasting to avoid the need for a loop. I'm following your original convention for angle definitions here, and finding the inverse rotation to determine the source location. The big difference here is that the input in degrees is converted to radians, since that's what the trigonometric functions expect:
angle = float(input('Enter Angle in Degrees: ')) * np.pi / 180.0
source_row = row_coord * np.cos(angle) - col_coord * np.sin(angle) + center_row
source_col = row_coord * np.sin(angle) + col_coord * np.cos(angle) + center_col
If all the indices were guaranteed to fall within the input image, you wouldn't even need to pre-allocate the output. You could literally just do new = im[source_row, source_col]. However, you need to mask the indices:
mask = source_row >= 0 & source_row < im.shape[0] & source_col >= 0 & source_col < im.shape[1]
new[mask] = im[source_row[mask].round().astype(int), source_col[mask].round().astype(int)]
Affine Transforms
Now let's take a look at using Affine transforms. First you want to subtract the center from your coordinates. Let's say you have a column vector [[r], [c], [1]]. A translation to zero would be the matrix
[[r'] [[1 0 -rc] [[r]
[c'] = [0 1 -cc] . [c]
[1 ]] [0 0 1 ]] [1]]
Then the (backwards) rotation is applied:
[[r''] [[cos(a) -sin(a) 0] [[r']
[c''] = [sin(a) cos(a) 0] . [c']
[ 1 ]] [ 0 0 1]] [1 ]]
And finally, you need to translate back to center:
[[r'''] [[1 0 rc] [[r'']
[c'''] = [0 1 cc] . [c'']
[ 1 ]] [0 0 1]] [ 1 ]]
If you multiply these three matrices out in order from right to left, you get
[[cos(a) -sin(a) cc * sin(a) - rc * cos(a) + rc]
M = [sin(a) cos(a) -cc * cos(a) - rc * sin(a) + cc]
[ 0 0 1 ]]
If you build a full matrix of output coordinates rather than the subset arrays we started with, you can use np.matmul, a.k.a. the # operator to do the multiplication for you. There is no need for this level of complexity for such a simple case though:
matrix = np.array([[np.cos(angle), -np.sin(angle), col_center * np.sin(angle) - row_center * np.cos(angle) + row_center],
[np.sin(angle), np.cos(angle), -col_center * np.cos(angle) - row_center * np.sin(angle) + col_center],
[0, 0, 1]])
coord = np.ones((*im.shape, 3, 1))
coord[..., 0, :] = np.arange(im.shape[0]).reshape(-1, 1, 1, 1)
coord[..., 1, :] = np.arange(im.shape[1]).reshape(-1, 1, 1)
source = (matrix # coord)[..., :2, 0]
The remainder of the processing is fairly similar to the manual computations:
mask = (source >= 0 & source_row < im.shape).all(axis=-1)
new[mask] = im[source[0, mask].round().astype(int), source_col[1, mask].round().astype(int)]
I tried to implement Madphysicist's matrix multiplication method. Here's is the implementation, for those who care:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import numpy as np
import matplotlib.pyplot as plt
from pathlib import Path
path = Path(".")
img = plt.imread(path.resolve().parent / "img_align" / "faces_imgs" / "4.jpg")
angle = 15
def _transform(rot_mat, x, y):
"""
conveninece method for matrix multiplication
"""
return np.matmul(rot_mat, np.array([x, y, 1]))
def rotate(img, angle):
angle %= 360
angle = np.radians(angle)
new = np.zeros_like(img)
cx, cy = tuple(x / 2 for x in img.shape[:2])
# Angles are reverse as we are interpolating from destination to source
rot_mat = np.array(
[
[np.cos(-angle), -np.sin(-angle), 0],
[np.sin(-angle), np.cos(-angle), 0],
[0, 0, 1],
]
)
rot_mat[0, 2], rot_mat[1, 2], _ = _transform(rot_mat, -cx, -cy)
# build combined affine transformation matrrix
rot_mat[0, 2] += cx
rot_mat[1, 2] += cy
coord = np.ones((*img.shape, 3, 1)) # [576x336x3x3x1]
coord[..., 0, :] = np.arange(img.shape[0]).reshape(-1, 1, 1, 1)
coord[..., 1, :] = np.arange(img.shape[1]).reshape(-1, 1, 1)
source = (rot_mat # coord)[..., :2, 0]
x_mask = source[..., 0]
y_mask = source[..., 1]
mask = (
(x_mask >= 0)
& (x_mask < img.shape[0])
& (y_mask >= 0)
& (y_mask < img.shape[1])
).all(axis=-1)
# Clipping values to avoid IndexError
new[mask] = img[
x_mask[..., 0][mask].round().astype(int).clip(None, img.shape[0] - 1),
y_mask[..., 1][mask].round().astype(int).clip(None, img.shape[1] - 1),
]
plt.imsave("test.jpg", new)
if __name__ == "__main__":
rotate(img, angle)
I think this is what you are looking for
Properly rotate image in OpenCV?
Here is the code
ang = int(input("Enter the angle : "))
im = cv2.imread("Samples\\baboon.jpg", cv2.IMREAD_GRAYSCALE)
def rotimage(image):
row,col = image.shape[0:2]
center=tuple(np.array([col,row])/2)
rot_mat = cv2.getRotationMatrix2D(center,ang,1.0)
new_image = cv2.warpAffine(image, rot_mat, (col,row))
return new_image
new_image = rotimage(im)
cv2.imshow("1",new_image)
cv2.waitKey(0)
I have a specific python issue, that desperately needs to be sped up by avoiding the use of a loop, yet, I am at a loss as to how to do this. I need to read in a fits image, convert this to a numpy array (roughly, 2000 x 2000 elements in size), then for each element compute the statistics of a ring of elements around it.
As I have my code now, the statistics of the ring around the element is computed with a function using masks. This is fast but, of course, I call this function 2000x2000 times (the slow part).
I am relatively new to python. I think that using the mask function is clever, but I cannot find a way around individually addressing each element. Best of thanks for any help you can provide.
# First, the function computing the statistics within a ring
around the central pixel:<br/>
# flux = image intensity at pixel (i,j)<br/>
# rad1, rad2 = inner and outer radii<br/>
# array = image array<br/>_
def snr(flux, i, j, rad1, rad2, array):
a, b = i, j
nx, ny = array.shape
y, x = np.ogrid[-a:nx-a, -b:ny-b]
mask = (x*x + y*y >= rad1*rad1) & (x*x + y*y <= rad2*rad2)
Nmask = np.count_nonzero(mask)
noise = 0.6052697 * abs(Nmask * flux - sum(array[mask]))
return noise
# Now, the call to snr for each pixel in the array data1:<br/>_
frame1 = fits.open(in_frame, mode='readonly') # read in fits file
data1 = frame1[ext].data # convert to np array
ny, nx = data1.shape # array dimensions
noise1 = zeros((ny, nx), float) # empty array
r1 = 5 # inner radius (pixels)
r2 = 7 # outer radius (pixels)
# The function is fast, but calling it 2k x 2k times is not:
for j in range(ny):
for i in range(nx):
noise1[i,j] = der_snr(data1[i,j], i, j, r1, r2, data1)
The operation that you are trying to do can be expressed as an image convolution. Try something like this:
import numpy as np
import scipy.ndimage
from astropy.io import fits
def make_kernel(inner_radius, outer_radius):
if inner_radius > outer_radius:
raise ValueError
x, y = np.ogrid[-outer_radius:outer_radius + 1, -outer_radius:outer_radius + 1]
r2 = x * x + y * y
kernel = (r2 >= inner_radius * inner_radius) & (r2 <= outer_radius * outer_radius)
return kernel
in_frame = '<file path>'
ext = '...'
frame1 = fits.open(in_frame, mode='readonly')
data1 = frame1[ext].data
inner_radius = 5
outer_radius = 7
kernel = make_kernel(inner_radius, outer_radius)
n_kernel = np.count_nonzero(kernel)
conv = scipy.ndimage.convolve(data1, kernel, mode='constant')
noise1 = 0.6052697 * np.abs(n_kernel * data1 - conv)
I am currently completing a program in Pyhton (3.6) as per internal requirement. As part of it, I am having to loop through a colour image (3 bytes per pixel, R, G & B) and distort the image pixel by pixel.
I have the same code in other languages (C++, C#), and non-optimized code executes in about two seconds, while optimized code executes in less than a second. By non-optimized code I mean that the matrix multiplication is performed by a 10 line function I implemented. The optimized version just uses external libraries for multiplication.
In Python, this code takes close to 300 seconds. I canĀ“t think of a way to vectorize this logic or speed it up, as there are a couple of "if"s inside the nested loop. Any help would be greatly appreciated.
import numpy as np
#for test purposes:
#roi = rect.rect(0, 0, 1200, 1200)
#input = DCImage.DCImage(1200, 1200, 3)
#correctionImage = DCImage.DCImage(1200,1200,3)
#siteToImage= np.zeros((3,3), np.float32)
#worldToSite= np.zeros ((4, 4))
#r11 = r12 = r13 = r21 = r22 = r23 = r31 = r32 = r33 = 0.0
#xMean = yMean = zMean = 0
#tx = ty = tz = 0
#epsilon = np.finfo(float).eps
#fx = fy = cx = cy = k1 = k2 = p1 = p2 = 0
for i in range (roi.x, roi.x + roi.width):
for j in range (roi.y , roi.y + roi.height):
if ( (input.pixels [i] [j] == [255, 0, 0]).all()):
#Coordinates conversion
siteMat = np.matmul(siteToImage, [i, j, 1])
world =np.matmul(worldToSite, [siteMat[0], siteMat[1], 0.0, 1.0])
xLocal = world[0] - xMean
yLocal = world[1] - yMean
zLocal = z_ortho - zMean
#From World to camera
xCam = r11*xLocal + r12*yLocal + r13*zLocal + tx
yCam = r21*xLocal + r22*yLocal + r23*zLocal + ty
zCam = r31*xLocal + r32*yLocal + r33*zLocal + tz
if (zCam > epsilon or zCam < -epsilon):
xCam = xCam / zCam
yCam = yCam / zCam
#// DISTORTIONS
r2 = xCam*xCam + yCam*yCam
a1 = 2*xCam*yCam
a2 = r2 + 2*xCam*xCam
a3 = r2 + 2*yCam*yCam
cdist = 1 + k1*r2 + k2*r2*r2
u = int((xCam * cdist + p1 * a1 + p2 * a2) * fx + cx + 0.5)
v = int((yCam * cdist + p1 * a3 + p2 * a1) * fy + cy + 0.5)
if (u>=0 and u<correctionImage.width and v>=0 and v < correctionImage.height):
input.pixels [i] [j] = correctionImage.pixels [u][v]
You normally vectorize this kind of thing by making a displacement map.
Make a complex image where each pixel has the value of its own coordinate, apply the usual math operations to compute whatever transform you want, then apply the map to your source image.
For example, in pyvips you might write:
import sys
import pyvips
image = pyvips.Image.new_from_file(sys.argv[1])
# this makes an image where pixel (0, 0) (at the top-left) has value [0, 0],
# and pixel (image.width, image.height) at the bottom-right has value
# [image.width, image.height]
index = pyvips.Image.xyz(image.width, image.height)
# make a version with (0, 0) at the centre, negative values up and left,
# positive down and right
centre = index - [image.width / 2, image.height / 2]
# to polar space, so each pixel is now distance and angle in degrees
polar = centre.polar()
# scale sin(distance) by 1/distance to make a wavey pattern
d = 10000 * (polar[0] * 3).sin() / (1 + polar[0])
# and back to rectangular coordinates again to make a set of vectors we can
# apply to the original index image
distort = index + d.bandjoin(polar[1]).rect()
# distort the image
distorted = image.mapim(distort)
# pick pixels from either the distorted image or the original, depending on some
# condition
result = (d.abs() > 10 or image[2] > 100).ifthenelse(distorted, image)
result.write_to_file(sys.argv[2])
That's just a silly wobble pattern, but you can swap it for any distortion you want. Then run as:
$ /usr/bin/time -f %M:%e ./wobble.py ~/pics/horse1920x1080.jpg x.jpg
54572:0.31
300ms and 55MB of memory on this two-core, 2015 laptop to make:
After much testing, the only way to speed the function without writing it in C++ was dissassembling it and vectorizing it. The way to do it in this particular instance is to create an array with the valid indexes at the beginning of the funcion and use them as tuples to index the final solution.
subArray[roi.y:roi.y+roi.height,roi.x:roi.x+roi.width,] = input.pixels[roi.y:roi.y+roi.height,roi.x:roi.x+roi.width,]
#Calculate valid XY indexes
y_index, x_index = np.where(np.all(subArray== np.array([255,0,0]), axis=-1))
#....
#do stuff
#....
#Join result values with XY indexes
ij_xy = np.column_stack((i, j, y_index, x_index))
#Only keep valid ij values
valids_ij_xy = ij_xy [(ij_xy [:,0] >= 0) & (ij_xy [:,0] < correctionImage.height) & (ij_xy [:,1] >= 0) & (ij_xy [:,1] < correctionImage.width)]
#Assign values
input.pixels [tuple(np.array(valids_ij_xy [:,2:]).T)] = correctionImage.pixels[tuple(np.array(valids_ij_xy [:,:2]).T)]
I'm trying to denoise an image by averaging all pixels that are within a certain euclidian distance using the following loop. Currently this loop takes 65 seconds and likely needs performed thousands of times. Is there a way to accomplish this in python without a prohibitive run time? Any help would be greatly appreciated.
for row in range(0, width):
for column in range(0, height):
if euclid_dist(point, pix[row,column]) <= threshold:
to_average[(row,column)] = pix[row, column]
euclid_dist is defined as the following:
def euclid_dist(tuple1, tuple2):
tot_sq = 0
for num1, num2 in zip(tuple1, tuple2):
tot_sq += (num1 + num2)**2
return math.sqrt(tot_sq)
If you want to just average everything in a circle (equally, rather than with a gaussian), you can make a hard circle kernel, then convolve your image with it as below.
# make a noisy circle thing
img = np.random.randint(100, 200, (200, 200), dtype=np.uint8)
xx, yy = np.meshgrid(np.arange(-100, 100), np.arange(-100, 100))
img = img + 10 * (xx**2 + yy**2 < 50**2)
plt.imshow(img)
# make a non-standard kernel
radius = 10
kernel_size = 2 * int(radius) + 1 # odd
xy = np.arange(-kernel_size//2 + 1, kernel_size//2 + 1)
xx, yy = np.meshgrid(xy, xy)
kernel = np.zeros((kernel_size,) * 2)
kernel[xx**2 + yy**2 <= radius**2] = 1
plt.imshow(kernel)
# convolve the two, depending on the mode, it will change the dimensions of the output
plt.imshow(sig.convolve2d(img, kernel, mode='valid'))
If you want to de-noise, you could also use a gaussian kernel, which is a bit more common. It's more simply called "Gaussian blurring" then.