cv2.remap height must be multiple of 8?

asked 2014-08-05 09:53:29 -0500

Gertlex gravatar image

I am using the cv2.remap function (OpenCV on OS X 10.9.4) to warp rectangular images with an alpha channel into arbitrary quads. I am encountering an issue where along the bottom of the image, pixels of a uniform color sometimes appear behind my rotated image. As a test case for my code, I warp a red square. An example result for a 100x100 output square:

image description

Notice the aliasing at the bottom corner; the strip along the bottom is replaced where the rotated square intersects it. Thus the strip is 'behind' the imagery.

The test case has allowed me to play with numbers, and I have discovered that this issue doesn't occur if new image's height is a multiple of 8. For example, if I change the output image to be 104 pixels high, the bar disappears (but now I have undesirable padding at the bottom). The size of the bar varies with the remainder of H/8 (i.e. a height of 103 creates the thickest bar).

image description

Am I correct in suspecting this is a bug? I have not directly played with the C/C++(?) API for this functionality to see if the issue exists there too.

A simpler, modified version of my code and test is below. The use of griddata along with remap is adapted from this answer on Stack Overflow. Assuming one has the various python packages used, (Pillow, scipy, cv2, numpy), this script should work to produce the above images. The later is created if you change ysize to 104.

from PIL import Image
import cv2
import numpy as np
from scipy.interpolate import griddata

def remap_image_from_pts(img, oldmap, newmap, maxx, maxy):
    """Remaps img using sparse maps.

    img : cv2.Image
    oldmap : 2D array-like
        Sparse map of points corresponding to original image.
    newmap : 2D array-like
        Sparse map of points corresponding to warped image.
    maxx, maxy : int
        Dimensions of resulting dense mappings. (Can be inferred from newmap)

        Remapped image

    grid_x, grid_y = np.mgrid[0:maxx:maxx*1j, 0:maxy:maxy*1j]
    grid_z = griddata(newmap, oldmap, (grid_x, grid_y), method='linear')
    print grid_z.shape
    map_x = np.append([], [ar[:,0] for ar in grid_z]).reshape(maxx, maxy)
    map_y = np.append([], [ar[:,1] for ar in grid_z]).reshape(maxx, maxy)
    map_x_32 = map_x.astype('float32')
    map_y_32 = map_y.astype('float32')

    remappedpts = cv2.transpose(
            cv2.remap(img, map_x_32, map_y_32, cv2.INTER_CUBIC, borderValue=0, borderMode=cv2.BORDER_CONSTANT))

    pil_im = Image.fromarray(remappedpts, mode='RGBA')
    return pil_im

def test_remap_image_from_pts():
    # Should create a red square and output a smaller rotated red square.

    size = 100
    imgPIL ='RGB', (size,size), 'red')

    # convert to cv2 compatible format and add alpha channel
    img = np.array(imgPIL, dtype=np.uint8)
    img_rgb = cv2.cvtColor(img, cv2.COLOR_RGB2RGBA)

    oldmap = np.array([np.array(i) for i in [[0,100], [0,0], [100,0],[100,100]]])
    newmap = np.array([np.array(i) for i in [ [0,80],[20,0],[100,20], [80,100]]])

    ysize = 100
    maxx, maxy = (size, ysize)

    outputPIL = remap_image_from_pts ...
edit retag flag offensive close merge delete