1

Im trying to horizontally warp an image. Every point with an x coord less than 960 should have an x prime lower than the original x. Vice versa for x coords above 960. So basically the image should be stretched out from the middle. original goal I have an algorithm that takes an x value and spits out x prime and it works as it should.

for x in range(0, 1920, 100):
    print(x.__str__() + " -> " + get_x_prime(x).__str__())

0 -> -464.13562305627875
100 -> -199.145754035633
200 -> 11.279005891624138
300 -> 185.45085122321177
400 -> 334.6324984422464
500 -> 466.1992357276346
600 -> 585.2553847864954
700 -> 695.525415167129
800 -> 799.8795490234673
900 -> 900.6659066871005
1000 -> 999.9383139500362
1100 -> 1099.6303840786015
1200 -> 1201.7087745106946
1300 -> 1308.3321015276756
1400 -> 1422.0437129077707
1500 -> 1546.0371984821832
1600 -> 1684.558583983833
1700 -> 1843.5627842206427
1800 -> 2031.8594162549412
1900 -> 2263.2577449729893

But when I try to create the x map, it inverses the algorithm so it squishes the image instead of stretching it out.

map_x = 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 prime map
for i in range(map_x.shape[0]):      
    map_x[i,:] = [get_x_prime(x) for x in range(map_x.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])]

dst = cv2.remap(img, map_x, map_y, cv2.INTER_LINEAR)

cv2.imwrite("new_image.jpg", dst)

problem

  • What is your mathematical formula for your mapping? Is your mapping an **inverse** mapping, that is, input = Func(output)? – fmw42 Jun 11 '22 at 18:26
  • @fmw42 No it is a tan function in `get_x_prime()'. This function works just fine (except for values near 960 but whatever). In the first code block its formatted like this: input -> output. The output should be the x prime that the original x is mapped to. – monopoly_lover Jun 11 '22 at 18:48
  • The map should sequence over the output and map to the input. Is that what you did? – fmw42 Jun 11 '22 at 18:52
  • Here is an example of what I have done. See https://stackoverflow.com/questions/69953948/how-make-eye-and-nose-bigger-or-smaller-in-opencv-and-python/70042527#70042527 or https://stackoverflow.com/questions/70090460/how-would-i-warp-text-around-an-images-edges/70104312#70104312 – fmw42 Jun 11 '22 at 19:04
  • 1
    to clarify: your mapping, as you have defined it, is a **forward** mapping, but `remap` and other warps need an **inverse** mapping, because they calculate the **source for every destination pixel**. that is why they need the inverse map, to look up where a pixel **comes from** -- if you can analytically invert your mapping (which is the case for whatever you do with a `tan`), simply fill the xymap with that then – Christoph Rackwitz Jun 11 '22 at 22:34
  • @Christoph Rackwitz. The thing that may confuse people is that the documentation for remap say "(x,y)=(mapx(x,y),mapy(x,y))", which may lead one to believe it is out(x,y)=in(mapx(x,y),mapy(x,y)) but is actually in(x,y)=out(mapx(x,y),mapy(x,y)). At least that seems confusing to me although I knew it required an inverse mapping. – fmw42 Jun 11 '22 at 23:43
  • no, what they say is correct and that's exactly how the lookup is done. out=dst, in=src. for each dst pixel at (x,y), the maps are consulted, and the mapped coordinates are used for the lookup in src. -- affine and perspective warps have flags to *not invert* the given matrix. they usually invert the matrix because the matrix represents the forward transformation and these functions need the inverse. – Christoph Rackwitz Jun 11 '22 at 23:58
  • NB: approaches to invert an xymap that isn't derived from an analytic formulation: https://stackoverflow.com/questions/41703210/inverting-a-real-valued-index-grid – Christoph Rackwitz Jun 12 '22 at 00:02
  • @ChristophRackwitz so if my x prime equation was `x_prime = tan(x)`, then I need to inverse it by doing `x = arctan(x_prime)` – monopoly_lover Jun 12 '22 at 21:01
  • looks correct. the xymap then contains the `x` (source pixel) to sample for every `x'` (destination pixel) – Christoph Rackwitz Jun 12 '22 at 23:23
  • @ChristophRackwitz what if it is unrealistic (super duper complex) to inverse the math function? Is there any other way to use forward mapping to accomplish the same thing? – monopoly_lover Jun 13 '22 at 01:53
  • yes, as linked above, there are numerical methods – Christoph Rackwitz Jun 13 '22 at 06:45

1 Answers1

1

Here is one way to achieve that kind of stretch in Python/OpenCV. I am not sure it is exactly what you want. But you can modify as desired.

What I do is a second order (non-linear) stretch from the center outward in the horizontal dimension.

The stretch is of the form: I=O + constant * O^2; I=input and O=output (since we need an inverse mapping)

The first term is linear and reproduces the input if no other term is included. The second term is second order (non-linear) and produces a stretch that is larger the further from the center in the horizontal direction.

This increases the width. So I crop the center to the size of the input width.

Input:

enter image description here

import numpy as np
import cv2
import math

# read input
img = cv2.imread("red_sky.jpg")
h_in, w_in = img.shape[:2]
x_icent = w_in / 2

# specify desired output width zoom factor
zoom = 3   # adjust as desired
h_out = h_in
w_out = zoom*h_in
x_ocent = w_out / 2
w_out = int(w_out)

# set up the x and y maps as float32
map_x = np.zeros((h_out, w_out), np.float32)
map_y = np.zeros((h_out, w_out), np.float32)

# create map with the non-linear stretch (second order) distortion formula
# I=O + constant * O^2; I=input and O=output
# use Imax with Omax;
# I=O + (Imax-Omax)/(Omax)^2 * O^2

for y in range(h_out):
    YY = y
    for x in range(w_out):
        X = (x - x_ocent)
        XX = X + math.copysign(X*X, X) * (x_icent-x_ocent)/(x_ocent*x_ocent) + x_icent
        map_x[y, x] = XX
        map_y[y, x] = YY

# do the remap
stretched = cv2.remap(img, map_x, map_y, cv2.INTER_CUBIC, borderMode = cv2.BORDER_CONSTANT, borderValue=(0,0,0))

# crop the center to size of input
x_half = w_in // 2
x_ocent = int(x_ocent)
result = stretched[0:h_in, x_ocent-x_half:x_ocent+x_half]

# save results
cv2.imwrite("red_sky_stretched.jpg", result)

# display images
cv2.imshow('img', img)
cv2.imshow('stretched', stretched)
cv2.imshow('result', result)
cv2.waitKey(0)
cv2.destroyAllWindows()

Stretched:

enter image description here

Stretched and Center Cropped:

enter image description here

fmw42
  • 46,825
  • 10
  • 62
  • 80