2

I am trying to horizontally stretch an image in a very specific way. Each x prime coordinate should follow a tangent path with respect to the original x coordinate. I believe there are two ways to do this:

  1. Inverse the tangent function and map it normally
  2. Map the tangent function and then inverse the mapping

Using this answer for map inversion, Im trying to figure out why the two images are not the same. I know that the first method gives me the correct image that I'm looking for, so why doesnt the second method work? Is it because of the "limited precision" that @ChristophRackwitz commented on the answer?

import cv2
import glob
import numpy as np
import math

A = -1010
B = -3.931
C = 5.258
D = 978.3

M = -193.8
N = 1740

def get_tan_func_value(x):
    return A * math.tan((((x-N)/M)+B)/C) + D

def get_inverse_tan_func_value(x):
    return M * (C*math.atan((x-D)/A) - B) + N

# answer from linked post
def invert_map(F, shape):
    I = np.zeros_like(F)
    I[:,:,1], I[:,:,0] = np.indices(shape)
    P = np.copy(I)
    for i in range(10):
        P += I - cv2.remap(F, P, None, interpolation=cv2.INTER_LINEAR)
    return P

# import image
images = glob.glob('*.jpg')
img = cv2.imread(images[0])
h,  w = img.shape[:2]

map_x_tan = np.zeros((img.shape[0], img.shape[1]), dtype=np.float32)
map_x_inverse_tan = np.zeros((img.shape[0], img.shape[1]), dtype=np.float32)
map_y = np.zeros((img.shape[0], img.shape[1]), dtype=np.float32)

# x tan function map
for i in range(map_x_tan.shape[0]):
    map_x_tan[i,:] = [get_tan_func_value(x) for x in range(map_x_tan.shape[1])]

# x inverse tan function map
for i in range(map_x_inverse_tan.shape[0]):
    map_x_inverse_tan[i,:] = [get_inverse_tan_func_value(x) for x in range(map_x_inverse_tan.shape[1])]

# default y map
for j in range(map_y.shape[1]):              
    map_y[:,j] = [y for y in range(map_y.shape[0])]

# convert x tan map to 2 channel (x,y) map
(xymap_tan, _) = cv2.convertMaps(map1=map_x_tan, map2=map_y, dstmap1type=cv2.CV_32FC2)

# invert the 2 channel x tan map
xymap_inverted = invert_map(xymap_tan, (h,w))

# remap and write the target image (inverse tan function with normal map)
target = cv2.remap(img, map_x_inverse_tan, map_y, cv2.INTER_LINEAR)
cv2.imwrite("target.jpg", target)

# remap and write the attempted image (normal tan function with inverted map) 
attempt = cv2.remap(img, xymap_inverted, None, cv2.INTER_LINEAR)
cv2.imwrite("attempt.jpg", attempt)

Method 1: Target Image target

Method 2: Attempt Image attempt

The results show that the attempt (normal tan function with inverted map) has less stretching near the edges of the image than expected. Almost everywhere else on the images are identical except the edges. I did not post the original picture to save space.

Christoph Rackwitz
  • 11,317
  • 4
  • 27
  • 36
  • I cant understand the differences between those 2 images u shared – Yunus Temurlenk Jun 16 '22 at 06:32
  • I played around with the number of iterations. the edges flip-flop around, alternating between solutions, but converging... slowly. I wonder if that could be improved. at 20-30 iterations, I still see problems. I can't explain yet what's going on. – Christoph Rackwitz Jun 16 '22 at 14:43
  • here are some improvements for how you define the maps: https://gist.github.com/crackwitz/67f76f8a9eff21476b080c06d20660d0 (don't touch individual elements of an array if there's numpy) – Christoph Rackwitz Jun 16 '22 at 14:52
  • @YunusTemurlenk try saving the two images and swapping back and forth between them. – monopoly_lover Jun 16 '22 at 18:39

1 Answers1

3

I've played around with that invert_map procedure. It seems slightly susceptible to oscillation.

use this instead:

def invert_map(F):
    (h, w) = F.shape[:2] # (h, w, 2), "xymap"
    I = np.zeros_like(F)
    I[:,:,1], I[:,:,0] = np.indices((h,w)) # identity map
    P = np.copy(I)
    for i in range(10):
        correction = I - cv2.remap(F, P, None, interpolation=cv2.INTER_LINEAR)
        P += correction * 0.5
    return P

I simply damped the correction by 0.5, which makes the fixed point iteration tamer, converging a lot faster too.

In my experiments with your tan map, I've found that 5-10 iterations are good enough already, and there's no further progress in further iterations.

Entire notebook of my explorations: https://gist.github.com/crackwitz/67f76f8a9eff21476b080c06d20660d0

Feature request: https://github.com/opencv/opencv/issues/22120

Christoph Rackwitz
  • 11,317
  • 4
  • 27
  • 36