11

I'd like to use the same affine matrix M on some individual (x,y) points as I use on images with cv2.warpAffine. It seems cv2.transform is the way to go . When I try send an Nx2 matrix of points I get negged (

   src = np.array([
        [x1,y1],[x2,y2],[x3,y3],[x4,y4]],  dtype = "float32")
    print('source shape '+str(src.shape))
    dst=cv2.transform(src,M)

cv2.error: /home/jeremy/sw/opencv-3.1.0/modules/core/src/matmul.cpp:1947: error: (-215) scn == m.cols || scn + 1 == m.cols in function transform

I can get the transform I want just using numpy arithmetic :

    dst = np.dot(src,M[:,0:2]) +M[:,2]
    print('dest:{}'.format(dst))

But would like to understand whats going on . The docs say that cv2.transform wants a number of channels equal to number of columns in M but I'm not clear what the channels would be - maybe an 'x' channel and 'y' channel, but then would would the third be, and what would the different rows signify?

jeremy_rutman
  • 3,552
  • 4
  • 28
  • 47
  • 1
    Often times when OpenCV expects points they like them in the form `np.array([ [[x1, y1]], [[x2, y2]], ... ])`. They usually want that for point transformations---a single column or row vector with length as the number of points and channels equivalent to the number of coordinates. The third coordinate could be a coordinate in a third dimension---transformation matrices aren't just for 2D transformations after all. – alkasm Jun 06 '17 at 00:29
  • thanks that did the trick , i had the dimension order backwards mentally, c*h*w instead of h*w*c, too much caffe – jeremy_rutman Jun 06 '17 at 09:36
  • not related to the question itself, but the numpy equivalent code will run fine but is not the correct transformation that you want. the numpy euivalent of cv2.transform would be `(np.dot(M[:,:2], a.T)+M[:,2].reshape(2,1)).T` – thisisbhavin Dec 28 '19 at 07:13

2 Answers2

16

OpenCV on Python often wants points in the form

np.array([ [[x1, y1]], ..., [[xn, yn]] ])

This is not clear in the documentation for cv2.transform() but is more clear in the documentation for other functions that use points, like cv2.perspectiveTransform() where they mention coordinates to be on separate channels:

src – input two-channel or three-channel floating-point array

Transforms can also be used in 3D (using a 4x4 perspective transformation matrix) so that would explain the ability to use two- or three-channel arrays in cv2.transform().

alkasm
  • 22,094
  • 5
  • 78
  • 94
  • 1
    For the use case mentioned above (2D transform of an Nx2 matrix of points), one shorthand is `dst = cv2.transform( np.array([src]),M)[0]` – Jerod Jun 22 '18 at 14:10
  • Slightly better: `cv2.transform(src[None, ...], M)[0]`. This creates a view to the `src` array, while `np.array([src])` creates an additional array which duplicates the memory and takes around 10x longer. – aerobiomat May 04 '22 at 14:42
1

The channel is the last dimension of the source array. Let's read the docs of cv2.transform() at the beginning.enter image description here


To the question:

Because the function transforms each element from the parameter src, the dimension of src is required to be bigger than 2.

import cv2
import numpy as np

rotation_mat = np.array([[0.8660254, 0.5, -216.41978046], [-0.5, 0.8660254, 264.31038357]]) # 2x3

rotate_box = np.array([[410, 495], [756, 295], [956, 642], [610, 842]]) # 2x2
result_box = cv2.transform(rotate_box, rotation_mat) # error: (-215:Assertion failed) scn == m.cols || scn + 1 == m.cols in function 'transform'

The reason is the dimension of each element of rotate_box is (2,). The transform by multiplication on matrices can not proceed.

To another answer: As long as the last dimension fits, other dimensions do not matter. Continue the above snippet:

rotate_box_1 = np.array([rotate_box]) # 1x4x2
result_box = cv2.transform(rotate_box_1, rotation_mat) # 1x4x2

rotate_box_2 = np.array([[[410, 495]], [[756, 295]], [[956, 642]], [[610, 842]]]) # 4x1x2
result_box = cv2.transform(rotate_box_2, rotation_mat) # 4x1x2

To reader: Note the shape returned by cv2.transform() is the same as the src.

Tengerye
  • 1,796
  • 1
  • 23
  • 46