-
-
Save jackdoerner/3dc8ece00deea604b3ae to your computer and use it in GitHub Desktop.
""" | |
riesz_pyramid.py | |
Conversion between Riesz and Laplacian image pyramids | |
Based on the data structures and methodoligies described in: | |
Riesz Pyramids for Fast Phase-Based Video Magnification | |
Neal Wadhwa, Michael Rubinstein, Fredo Durand and William T. Freeman | |
Computational Photography (ICCP), 2014 IEEE International Conference on | |
Copyright (c) 2016 Jack Doerner | |
Permission is hereby granted, free of charge, to any person obtaining a copy | |
of this software and associated documentation files (the "Software"), to deal | |
in the Software without restriction, including without limitation the rights | |
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
copies of the Software, and to permit persons to whom the Software is | |
furnished to do so, subject to the following conditions: | |
The above copyright notice and this permission notice shall be included in | |
all copies or substantial portions of the Software. | |
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
THE SOFTWARE. | |
""" | |
import numpy, math | |
import scipy, scipy.signal | |
#riesz_band_filter = numpy.asarray([[-0.5, 0, 0.5]]) | |
#riesz_band_filter = numpy.asarray([[-0.2,-0.48, 0, 0.48,0.2]]) | |
riesz_band_filter = numpy.asarray([[-0.12,0,0.12],[-0.34, 0, 0.34],[-0.12,0,0.12]]) | |
def laplacian_to_riesz(pyr): | |
newpyr = {'I':pyr[:-1], 'R1':[], 'R2':[]} | |
for ii in range(len(pyr) - 1): | |
newpyr['R1'].append( scipy.signal.convolve2d(pyr[ii], riesz_band_filter, mode='same', boundary='symm') ) | |
newpyr['R2'].append( scipy.signal.convolve2d(pyr[ii], riesz_band_filter.T, mode='same', boundary='symm') ) | |
newpyr['base'] = pyr[-1] | |
return newpyr | |
def riesz_to_spherical(pyr): | |
newpyr = {'A':[],'theta':[],'phi':[],'Q':[],'base':pyr['base']} | |
for ii in range(len(pyr['I']) ): | |
I = pyr['I'][ii] | |
R1 = pyr['R1'][ii] | |
R2 = pyr['R2'][ii] | |
A = numpy.sqrt(I*I + R1*R1 + R2*R2) | |
theta = numpy.arctan2(R2,R1) | |
Q = R1 * numpy.cos(theta) + R2 * numpy.sin(theta) | |
phi = numpy.arctan2(Q,I) | |
newpyr['A'].append( A ) | |
newpyr['theta'].append( theta ) | |
newpyr['phi'].append( phi ) | |
newpyr['Q'].append( Q ) | |
return newpyr | |
def riesz_spherical_to_laplacian(pyr): | |
newpyr = [] | |
for ii in range(len(pyr['A'])): | |
newpyr.append( pyr['A'][ii] * numpy.cos( pyr['phi'][ii] ) ) | |
newpyr.append(pyr['base']) | |
return newpyr |
import numpy | |
def symmetrical_boundary(img): | |
#manually set up a symmetrical boundary condition so we can use fftconvolve | |
#but avoid edge effects | |
(h,w) = img.shape | |
imgsymm = numpy.empty((h*2, w*2)) | |
imgsymm[h/2:-(h+1)/2, w/2:-(w+1)/2] = img.copy() | |
imgsymm[0:h/2, 0:w/2] = img[0:h/2, 0:w/2][::-1,::-1].copy() | |
imgsymm[-(h+1)/2:, -(w+1)/2:] = img[-(h+1)/2:, -(w+1)/2:][::-1,::-1].copy() | |
imgsymm[0:h/2, -(w+1)/2:] = img[0:h/2, -(w+1)/2:][::-1,::-1].copy() | |
imgsymm[-(h+1)/2:, 0:w/2] = img[-(h+1)/2:, 0:w/2][::-1,::-1].copy() | |
imgsymm[h/2:-(h+1)/2, 0:w/2] = img[:, 0:w/2][:,::-1].copy() | |
imgsymm[h/2:-(h+1)/2, -(w+1)/2:] = img[:, -(w+1)/2:][:,::-1].copy() | |
imgsymm[0:h/2, w/2:-(w+1)/2] = img[0:h/2, :][::-1,:].copy() | |
imgsymm[-(h+1)/2:, w/2:-(w+1)/2] = img[-(h+1)/2:, :][::-1,:].copy() | |
return imgsymm |
""" | |
rp_laplacian_like.py | |
Conversion between image and laplacian-like pyramids | |
Based on the data structures and methodoligies described in: | |
Riesz Pyramids for Fast Phase-Based Video Magnification | |
Neal Wadhwa, Michael Rubinstein, Fredo Durand and William T. Freeman | |
Computational Photography (ICCP), 2014 IEEE International Conference on | |
Copyright (c) 2016 Jack Doerner | |
Permission is hereby granted, free of charge, to any person obtaining a copy | |
of this software and associated documentation files (the "Software"), to deal | |
in the Software without restriction, including without limitation the rights | |
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
copies of the Software, and to permit persons to whom the Software is | |
furnished to do so, subject to the following conditions: | |
The above copyright notice and this permission notice shall be included in | |
all copies or substantial portions of the Software. | |
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
THE SOFTWARE. | |
""" | |
import numpy, cv2, scipy.signal | |
from rp_boundary import * | |
lowpass = numpy.asarray([ | |
[-0.0001, -0.0007, -0.0023, -0.0046, -0.0057, -0.0046, -0.0023, -0.0007, -0.0001], | |
[-0.0007, -0.0030, -0.0047, -0.0025, -0.0003, -0.0025, -0.0047, -0.0030, -0.0007], | |
[-0.0023, -0.0047, 0.0054, 0.0272, 0.0387, 0.0272, 0.0054, -0.0047, -0.0023], | |
[-0.0046, -0.0025, 0.0272, 0.0706, 0.0910, 0.0706, 0.0272, -0.0025, -0.0046], | |
[-0.0057, -0.0003, 0.0387, 0.0910, 0.1138, 0.0910, 0.0387, -0.0003, -0.0057], | |
[-0.0046, -0.0025, 0.0272, 0.0706, 0.0910, 0.0706, 0.0272, -0.0025, -0.0046], | |
[-0.0023, -0.0047, 0.0054, 0.0272, 0.0387, 0.0272, 0.0054, -0.0047, -0.0023], | |
[-0.0007, -0.0030, -0.0047, -0.0025, -0.0003, -0.0025, -0.0047, -0.0030, -0.0007], | |
[-0.0001, -0.0007, -0.0023, -0.0046, -0.0057, -0.0046, -0.0023, -0.0007, -0.0001] | |
]) | |
highpass = numpy.asarray([ | |
[0.0000, 0.0003, 0.0011, 0.0022, 0.0027, 0.0022, 0.0011, 0.0003, 0.0000], | |
[0.0003, 0.0020, 0.0059, 0.0103, 0.0123, 0.0103, 0.0059, 0.0020, 0.0003], | |
[0.0011, 0.0059, 0.0151, 0.0249, 0.0292, 0.0249, 0.0151, 0.0059, 0.0011], | |
[0.0022, 0.0103, 0.0249, 0.0402, 0.0469, 0.0402, 0.0249, 0.0103, 0.0022], | |
[0.0027, 0.0123, 0.0292, 0.0469, -0.9455, 0.0469, 0.0292, 0.0123, 0.0027], | |
[0.0022, 0.0103, 0.0249, 0.0402, 0.0469, 0.0402, 0.0249, 0.0103, 0.0022], | |
[0.0011, 0.0059, 0.0151, 0.0249, 0.0292, 0.0249, 0.0151, 0.0059, 0.0011], | |
[0.0003, 0.0020, 0.0059, 0.0103, 0.0123, 0.0103, 0.0059, 0.0020, 0.0003], | |
[0.0000, 0.0003, 0.0011, 0.0022, 0.0027, 0.0022, 0.0011, 0.0003, 0.0000] | |
]) | |
def getsize(img): | |
h, w = img.shape[:2] | |
return w, h | |
def build_laplacian(img, minsize=2, convolutionThreshold=500, dtype=numpy.float64): | |
img = dtype(img) | |
levels = [] | |
while (min(img.shape) > minsize): | |
if (img.size < convolutionThreshold): | |
convolutionFunction = scipy.signal.convolve2d | |
else: | |
convolutionFunction = scipy.signal.fftconvolve | |
w, h = getsize(img) | |
symmimg = symmetrical_boundary(img) | |
hp_img = convolutionFunction(symmimg, highpass, mode='same')[h/2:-(h+1)/2,w/2:-(w+1)/2] | |
lp_img = convolutionFunction(symmimg, lowpass, mode='same')[h/2:-(h+1)/2,w/2:-(w+1)/2] | |
levels.append(hp_img) | |
img = cv2.pyrDown(lp_img) | |
levels.append(img) | |
return levels | |
def collapse_laplacian(levels, convolutionThreshold=500): | |
img = levels[-1] | |
for ii in range(len(levels)-2,-1,-1): | |
lev_img = levels[ii] | |
img = cv2.pyrUp(img, dstsize=getsize(lev_img)) | |
if (img.size < convolutionThreshold): | |
convolutionFunction = scipy.signal.convolve2d | |
else: | |
convolutionFunction = scipy.signal.fftconvolve | |
w, h = getsize(img) | |
symmimg = symmetrical_boundary(img) | |
symmlev = symmetrical_boundary(lev_img) | |
img = convolutionFunction(symmimg, lowpass, mode='same')[h/2:-(h+1)/2,w/2:-(w+1)/2] | |
img += convolutionFunction(symmlev, highpass, mode='same')[h/2:-(h+1)/2,w/2:-(w+1)/2] | |
return img |
@jackdoerner Thank you for sharing this code!
I was able to make it run if casting all divisions inside indexes to int, for example imgsymm[int(h/2):int(-(h+1)/2)
. I am not sure if something has changed in python (3.9) that gives me the error TypeError: slice indices must be integers or None or have an index method
@tschnz There still appears to be some problems with the changes you made to the code: I am able to create what appears to be correct laplace or rectangular/cylindrical/spherical riesz pyramid and transform the riesz back to laplace. The problem appears when trying to reconstruct the original image from the laplace pyramid. This code makes a blurry img2 rather than a clone of the original img:
img2=rp_laplacian_like.collapse_laplacian(rp_laplacian_like.build_laplacian(img))
@t-fukiage Did you manage to change the code and was able to use it?
@evenlund
The following code is a modified version of rp_laplacian_like.py that I used.
Not sure if it still works as it was tested more than 3 years ago.
Hope this helps.
import numpy, scipy.signal
lowpass = numpy.asarray([
[-0.0001, -0.0007, -0.0023, -0.0046, -0.0057, -0.0046, -0.0023, -0.0007, -0.0001],
[-0.0007, -0.0030, -0.0047, -0.0025, -0.0003, -0.0025, -0.0047, -0.0030, -0.0007],
[-0.0023, -0.0047, 0.0054, 0.0272, 0.0387, 0.0272, 0.0054, -0.0047, -0.0023],
[-0.0046, -0.0025, 0.0272, 0.0706, 0.0910, 0.0706, 0.0272, -0.0025, -0.0046],
[-0.0057, -0.0003, 0.0387, 0.0910, 0.1138, 0.0910, 0.0387, -0.0003, -0.0057],
[-0.0046, -0.0025, 0.0272, 0.0706, 0.0910, 0.0706, 0.0272, -0.0025, -0.0046],
[-0.0023, -0.0047, 0.0054, 0.0272, 0.0387, 0.0272, 0.0054, -0.0047, -0.0023],
[-0.0007, -0.0030, -0.0047, -0.0025, -0.0003, -0.0025, -0.0047, -0.0030, -0.0007],
[-0.0001, -0.0007, -0.0023, -0.0046, -0.0057, -0.0046, -0.0023, -0.0007, -0.0001]
])
highpass = numpy.asarray([
[0.0000, 0.0003, 0.0011, 0.0022, 0.0027, 0.0022, 0.0011, 0.0003, 0.0000],
[0.0003, 0.0020, 0.0059, 0.0103, 0.0123, 0.0103, 0.0059, 0.0020, 0.0003],
[0.0011, 0.0059, 0.0151, 0.0249, 0.0292, 0.0249, 0.0151, 0.0059, 0.0011],
[0.0022, 0.0103, 0.0249, 0.0402, 0.0469, 0.0402, 0.0249, 0.0103, 0.0022],
[0.0027, 0.0123, 0.0292, 0.0469, -0.9455, 0.0469, 0.0292, 0.0123, 0.0027],
[0.0022, 0.0103, 0.0249, 0.0402, 0.0469, 0.0402, 0.0249, 0.0103, 0.0022],
[0.0011, 0.0059, 0.0151, 0.0249, 0.0292, 0.0249, 0.0151, 0.0059, 0.0011],
[0.0003, 0.0020, 0.0059, 0.0103, 0.0123, 0.0103, 0.0059, 0.0020, 0.0003],
[0.0000, 0.0003, 0.0011, 0.0022, 0.0027, 0.0022, 0.0011, 0.0003, 0.0000]
])
def build_laplacian(img, minsize=2, dtype=numpy.float64):
img = dtype(img)
levels = []
while (min(img.shape) > minsize):
convolutionFunction = scipy.signal.convolve2d
hp_img = convolutionFunction(img, highpass, mode='same',boundary='fill')
lp_img = convolutionFunction(img, lowpass, mode='same',boundary='fill')
levels.append(hp_img)
img = lp_img[0::2,0::2]
levels.append(img)
return levels
def collapse_laplacian(levels):
img = levels[-1]
for ii in range(len(levels)-2,-1,-1):
lev_img = levels[ii]
upimg = numpy.zeros((img.shape[0]*2,img.shape[1]*2))
upimg[0::2,0::2]=img.copy()*4
img = upimg[0:lev_img.shape[0],0:lev_img.shape[1]]
convolutionFunction = scipy.signal.convolve2d
img = convolutionFunction(img, lowpass, mode='same',boundary='fill')
img += convolutionFunction(lev_img, highpass, mode='same',boundary='fill')
return img
@t-fukiage That was working really nice - Thank you!
I got some small deviations close to the edges, but not a whole lot. I have experimented a bit with the borders, but the 'fill' method appears to be best. I also included the scipy.signal.fftconvolve method to speed up the processing for larger images and that appears to have similar edge deviations.
@evenlund
Good to hear that the code worked.
If you want to handle boundary with convolution, the following modification would do the trick:
def build_laplacian(img, minsize=2, dtype=numpy.float64):
img = dtype(img)
levels = []
while (min(img.shape) > minsize):
# convolutionFunction = scipy.signal.convolve2d
# hp_img = convolutionFunction(img, highpass, mode='same',boundary='fill')
# lp_img = convolutionFunction(img, lowpass, mode='same',boundary='fill')
hp_img = scipy.signal.convolve2d(numpy.pad(img, (highpass.shape[0]-1)//2, mode='reflect'), highpass, mode='valid')
lp_img = scipy.signal.convolve2d(numpy.pad(img, (lowpass.shape[0]-1)//2, mode='reflect'), lowpass, mode='valid')
levels.append(hp_img)
img = lp_img[0::2,0::2]
levels.append(img)
return levels
def collapse_laplacian(levels):
img = levels[-1]
for ii in range(len(levels)-2,-1,-1):
lev_img = levels[ii]
upimg = numpy.zeros((img.shape[0]*2,img.shape[1]*2))
upimg[0::2,0::2]=img.copy()*4
img = upimg[0:lev_img.shape[0],0:lev_img.shape[1]]
# convolutionFunction = scipy.signal.convolve2d
# img = convolutionFunction(img, lowpass, mode='same',boundary='fill')
# img += convolutionFunction(lev_img, highpass, mode='same',boundary='fill')
img = scipy.signal.convolve2d(numpy.pad(img, (lowpass.shape[0]-1)//2, mode='reflect'), lowpass, mode='valid')
img += scipy.signal.convolve2d(numpy.pad(lev_img, (highpass.shape[0]-1)//2, mode='reflect'), highpass, mode='valid')
return img
I used numpy.pad function to apply mirror (reflection) padding. The original code by @jackdoerner includes a function to apply symmetric padding, but the symmetric padding can cause another artifact in my modified code. (e.g., it amplifies energy at the top-left corner)
@jackdoerner Thank you for writing that up. I initially didn't understand it from what I read in the paper so I got the supplemental Matlab code, a C++ application (with pyrDown/pyrUp) and this Gist. All in all I got it now. Glad I could help. Out of curiosity: How did you interpolate the frames? Any papers or sources referencing to that technique? Sounds interesting!