Repository: kornia/kornia
Pull request: Fixes warp_affine and warp_perspective
### Description

This PR fixes known issues around warp_affine and warp_perspective. Fixes https://github.com/kornia/kornia/issues/747

The main issue is with align_corners - basically in order to match opencv it needs to be set by default to True.

- [x] Fix warp affine and add tests
- [x] Fix warp perspective and add tests
- [x] Refactor the interface of the functions `warp_perspective`, `warp_affine`, `center_crop`, `crop_and_resize` and `crop_by_boxes` in order to expose to the user the needed parameters by `grid_sample` [`mode`, `padding_mode`, `align_corners`].
- [x] The `align_corners` has been set by default to `None` that maps to `True` in case the user does not specify. This comes from the motivation to match the behavior of the warping functions with OpenCV.

### Status
**Work in progress**

### Types of changes
<!--- Put an `x` in all the boxes that apply, and remove the not applicable items -->
- [x] Bug fix (non-breaking change which fixes an issue)
- [x] Breaking change (fix or new feature that would cause existing functionality to change)
- [x] New tests added to cover the changes
- [x] Docstrings/Documentation updated


## PR Checklist
### PR Implementer
This is a small checklist for the implementation details of this PR.

If there are any questions regarding code style or other conventions check out our 
[summary](https://github.com/kornia/kornia/blob/master/CONTRIBUTING.rst).

- [x] Did you discuss the functionality or any breaking changes before ?
- [x] **Pass all tests**: did you test in local ? `make test`
- [x] Unittests: did you add tests for your new functionality ?
- [x] Documentations: did you build documentation ? `make build-docs`
- [x] Implementation: is your code well commented and follow conventions ? `make lint`
- [x] Docstrings & Typing: has your code documentation and typing ? `make mypy`
- [ ] Update notebooks & documentation if necessary

### KorniaTeam
<details>
  <summary>KorniaTeam workflow</summary>
  
  - [ ] Assign correct label
  - [ ] Assign PR to a reviewer
  - [ ] Does this PR close an Issue? (add `closes #IssueNumber` at the bottom if 
        not already in description)

</details>

### Reviewer
<details>
  <summary>Reviewer workflow</summary>

  - [ ] Do all tests pass? (Unittests, Typing, Linting, Documentation, Environment)
  - [ ] Does the implementation follow `kornia` design conventions?
  - [ ] Is the documentation complete enough ?
  - [ ] Are the tests covering simple and corner cases ?
 
</details>


Problem statement:
[Bug] warp_perspective does not return the input if used with identity matrix
## 🐛 Bug

The output of `kornia.geometry.warp_perspective` does not equal the input if the identity matrix is used.

## To Reproduce

Steps to reproduce the behavior:

```python
import torch
from kornia.geometry import warp_perspective

torch.manual_seed(0)

dsize = (32, 16)
src = torch.rand(1, 3, *dsize)
M = torch.eye(3).unsqueeze(0)

dst = warp_perspective(src, M, dsize)

mae = torch.mean(torch.abs(dst - src))
print(mae.item())
```

```
0.14952071011066437
```

## Expected behavior

```
0.0
```

## Environment

`kornia==0.4.1`



func warp_perspective outputs unexplained result
## 🐛 Bug
I tried the following code, the output is very confusing.

img = torch.ones((1,1,12,16),dtype=torch.float32)
homo = torch.tensor([[[1,0.5,0],[0,1,0],[0,0,1]]])
w_img = kornia.warp_perspective(img, homo, tuple(img.shape[2:]))
print(w_img)
[[[[0.2500, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.2500],
          [0.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000],
          [0.0000, 0.5000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000],
          [0.0000, 0.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000],
          [0.0000, 0.0000, 0.5000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000],
          [0.0000, 0.0000, 0.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000],
          [0.0000, 0.0000, 0.0000, 0.5000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000],
          [0.0000, 0.0000, 0.0000, 0.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000],
          [0.0000, 0.0000, 0.0000, 0.0000, 0.5000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000],
          [0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000],
          [0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.5000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000],
          [0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000, 0.5000]]]])
img_warp = kornia.tensor_to_image(w_img.byte())
print(img_warp)
[[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
 [0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1]
 [0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1]
 [0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1]
 [0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1]
 [0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1]
 [0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1]
 [0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1]
 [0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1]
 [0 0 0 0 0 1 1 0 1 1 1 1 1 1 1 1]
 [0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1]
 [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]]

Note that, w_img[0,0,9,7]==1.0, however, img_warp[9,7]=0.
 Morever, the result is not exactly the same as opencv (mainly on the borders)

Hints from issue discussion:
@pmeier I can confirm that happens the same in 0.4.0, 0.3.2, 0.3.1 but no in 0.3.0
@pmeier `align_corners` should be set to `True` - this change was introduced in https://github.com/kornia/kornia/pull/574

See: https://github.com/kornia/kornia/blob/master/kornia/geometry/transform/imgwarp.py#L48 it's now set False by default.
Not sure how much will affect other components.

This definitely needs better testing: https://github.com/kornia/kornia/blob/master/test/geometry/transform/test_imgwarp.py#L12
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions, and happy coding day :sunglasses:

@edgarriba This should not be closed by the bot, since the bug won't go away if no one has posted anything new.
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions, and happy coding day :sunglasses:

Commit message: remove DeprecationWarning type from warnings

Patch to review:
diff --git a/kornia/geometry/transform/imgwarp.py b/kornia/geometry/transform/imgwarp.py
index d828efa4e9..db67e4fe4e 100644
--- a/kornia/geometry/transform/imgwarp.py
+++ b/kornia/geometry/transform/imgwarp.py
@@ -1,4 +1,5 @@
 from typing import Tuple, Optional
+import warnings
 
 import torch
 import torch.nn.functional as F
@@ -12,7 +13,9 @@
 from kornia.geometry.transform.projwarp import (
     get_projective_transform
 )
-from kornia.testing import check_is_tensor
+from kornia.utils import create_meshgrid
+from kornia.geometry.linalg import transform_points
+
 
 __all__ = [
     "warp_perspective",
@@ -29,23 +32,9 @@
 ]
 
 
-def transform_warp_impl(src: torch.Tensor, dst_pix_trans_src_pix: torch.Tensor,
-                        dsize_src: Tuple[int, int], dsize_dst: Tuple[int, int],
-                        grid_mode: str, padding_mode: str,
-                        align_corners: bool) -> torch.Tensor:
-    """Compute the transform in normalized coordinates and perform the warping.
-    """
-    dst_norm_trans_src_norm: torch.Tensor = normalize_homography(
-        dst_pix_trans_src_pix, dsize_src, dsize_dst)
-
-    src_norm_trans_dst_norm = torch.inverse(dst_norm_trans_src_norm)
-    return homography_warp(src, src_norm_trans_dst_norm, dsize_dst, grid_mode, padding_mode,
-                           align_corners, True)
-
-
 def warp_perspective(src: torch.Tensor, M: torch.Tensor, dsize: Tuple[int, int],
                      flags: str = 'bilinear', border_mode: str = 'zeros',
-                     align_corners: bool = False) -> torch.Tensor:
+                     align_corners: bool = True) -> torch.Tensor:
     r"""Applies a perspective transformation to an image.
 
     The function warp_perspective transforms the source image using
@@ -65,7 +54,7 @@ def warp_perspective(src: torch.Tensor, M: torch.Tensor, dsize: Tuple[int, int],
           'bilinear' | 'nearest'. Default: 'bilinear'.
         border_mode (str): padding mode for outside grid values
           'zeros' | 'border' | 'reflection'. Default: 'zeros'.
-        align_corners(bool): interpolation flag. Default: False.
+        align_corners(bool): interpolation flag. Default: True.
 
     Returns:
         torch.Tensor: the warped input image :math:`(B, C, H, W)`.
@@ -74,8 +63,13 @@ def warp_perspective(src: torch.Tensor, M: torch.Tensor, dsize: Tuple[int, int],
        See a working example `here <https://kornia.readthedocs.io/en/latest/
        tutorials/warp_perspective.html>`_.
     """
-    check_is_tensor(src)
-    check_is_tensor(M)
+    if not isinstance(src, torch.Tensor):
+        raise TypeError("Input src type is not a torch.Tensor. Got {}"
+                        .format(type(src)))
+
+    if not isinstance(M, torch.Tensor):
+        raise TypeError("Input M type is not a torch.Tensor. Got {}"
+                        .format(type(M)))
 
     if not len(src.shape) == 4:
         raise ValueError("Input src must be a BxCxHxW tensor. Got {}"
@@ -85,15 +79,37 @@ def warp_perspective(src: torch.Tensor, M: torch.Tensor, dsize: Tuple[int, int],
         raise ValueError("Input M must be a Bx3x3 tensor. Got {}"
                          .format(M.shape))
 
-    # launches the warper
-    h, w = src.shape[-2:]
-    return transform_warp_impl(src, M, (h, w), dsize, flags, border_mode, align_corners)
+    # TODO: remove in kornia v0.6
+    message: str = (
+        "The align_corners flag has been inverted. By default now is set True "
+        "in order to match cv2.warpAffine. Set to False in order to keep your previous behaviour. "
+        "This warning will disappear in kornia >0.6.")
+    warnings.warn(message)
+
+    B, C, H, W = src.size()
+    h_out, w_out = dsize
+
+    # we normalize the 3x3 transformation matrix and convert to 3x4
+    dst_norm_trans_src_norm: torch.Tensor = normalize_homography(
+        M, (H, W), (h_out, w_out))  # Bx3x3
+
+    src_norm_trans_dst_norm = torch.inverse(dst_norm_trans_src_norm)  # Bx3x3
+
+    # this piece of code substitutes F.affine_grid since it does not support 3x3
+    grid = create_meshgrid(h_out, w_out, normalized_coordinates=True,
+                           device=src.device).to(src.dtype).repeat(B, 1, 1, 1)
+    grid = transform_points(src_norm_trans_dst_norm[:, None, None], grid)
+
+    return F.grid_sample(src, grid,
+                         align_corners=align_corners,
+                         mode=flags,
+                         padding_mode=border_mode)
 
 
 def warp_affine(src: torch.Tensor, M: torch.Tensor,
                 dsize: Tuple[int, int], flags: str = 'bilinear',
                 padding_mode: str = 'zeros',
-                align_corners: bool = False) -> torch.Tensor:
+                align_corners: bool = True) -> torch.Tensor:
     r"""Applies an affine transformation to a tensor.
 
     The function warp_affine transforms the source tensor using
@@ -111,7 +127,7 @@ def warp_affine(src: torch.Tensor, M: torch.Tensor,
           'bilinear' | 'nearest'. Default: 'bilinear'.
         padding_mode (str): padding mode for outside grid values
           'zeros' | 'border' | 'reflection'. Default: 'zeros'.
-        align_corners (bool): mode for grid_generation. Default: False.
+        align_corners (bool): mode for grid_generation. Default: True.
 
     Returns:
         torch.Tensor: the warped tensor with shape :math:`(B, C, H, W)`.
@@ -135,17 +151,27 @@ def warp_affine(src: torch.Tensor, M: torch.Tensor,
     if not (len(M.shape) == 3 or M.shape[-2:] == (2, 3)):
         raise ValueError("Input M must be a Bx2x3 tensor. Got {}"
                          .format(M.shape))
+
+    # TODO: remove in kornia v0.6
+    message: str = (
+        "The align_corners flag has been inverted. By default now is set True "
+        "in order to match cv2.warpPerspective. Set to False in order to keep your previous behaviour. "
+        "This warning will disappear in kornia >0.6.")
+    warnings.warn(message)
+
     B, C, H, W = src.size()
-    dsize_src = (H, W)
-    out_size = dsize
+
     # we generate a 3x3 transformation matrix from 2x3 affine
     M_3x3: torch.Tensor = convert_affinematrix_to_homography(M)
     dst_norm_trans_src_norm: torch.Tensor = normalize_homography(
-        M_3x3, dsize_src, out_size)
+        M_3x3, (H, W), dsize)
+
     src_norm_trans_dst_norm = torch.inverse(dst_norm_trans_src_norm)
+
     grid = F.affine_grid(src_norm_trans_dst_norm[:, :2, :],
-                         [B, C, out_size[0], out_size[1]],
+                         [B, C, dsize[0], dsize[1]],
                          align_corners=align_corners)
+
     return F.grid_sample(src, grid,
                          align_corners=align_corners,
                          mode=flags,
diff --git a/test/geometry/transform/test_imgwarp.py b/test/geometry/transform/test_imgwarp.py
index 9a4b2f6bc7..b5eaddd80e 100644
--- a/test/geometry/transform/test_imgwarp.py
+++ b/test/geometry/transform/test_imgwarp.py
@@ -8,46 +8,6 @@
 from torch.testing import assert_allclose
 
 
-@pytest.mark.parametrize("batch_shape", [(1, 1, 7, 32), (2, 3, 16, 31)])
-def test_warp_perspective_rotation(batch_shape, device, dtype):
-    # generate input data
-    batch_size, channels, height, width = batch_shape
-    alpha = 0.5 * kornia.pi * torch.ones(batch_size, device=device, dtype=dtype)  # 90 deg rotation
-
-    # create data patch
-    patch = torch.rand(batch_shape, device=device, dtype=dtype)
-
-    # create transformation (rotation)
-    M = torch.eye(3, device=device, dtype=dtype).repeat(batch_size, 1, 1)  # Bx3x3
-    M[:, 0, 0] = torch.cos(alpha)
-    M[:, 0, 1] = -torch.sin(alpha)
-    M[:, 1, 0] = torch.sin(alpha)
-    M[:, 1, 1] = torch.cos(alpha)
-
-    # apply transformation and inverse
-    _, _, h, w = patch.shape
-    patch_warped = kornia.warp_perspective(patch, M, dsize=(height, width), align_corners=True)
-    patch_warped_inv = kornia.warp_perspective(
-        patch_warped, torch.inverse(M), dsize=(height, width), align_corners=True)
-
-    # generate mask to compute error
-    mask = torch.ones_like(patch)
-    mask_warped_inv = kornia.warp_perspective(
-        kornia.warp_perspective(patch, M, dsize=(height, width), align_corners=True),
-        torch.inverse(M),
-        dsize=(height, width), align_corners=True)
-
-    assert_allclose(mask_warped_inv * patch,
-                    mask_warped_inv * patch_warped_inv, rtol=1e-4, atol=1e-4)
-
-
-def test_warp_perspective_gradcheck(device, dtype):
-    H, W = 5, 5
-    patch = torch.rand(1, 1, 5, 5, device=device, dtype=torch.float64, requires_grad=True)
-    M = kornia.eye_like(3, patch)
-    assert gradcheck(kornia.warp_perspective, (patch, M, (H, W),), raise_exception=True)
-
-
 @pytest.mark.parametrize("batch_size", [1, 2, 5])
 def test_get_perspective_transform(batch_size, device, dtype):
     # generate input data
@@ -132,7 +92,178 @@ def test_rotation_matrix2d(batch_size, device, dtype):
         raise_exception=True)
 
 
+class TestWarpAffine:
+    def test_smoke(self, device, dtype):
+        batch_size, channels, height, width = 1, 2, 3, 4
+        aff_ab = torch.eye(2, 3, device=device, dtype=dtype)[None]  # 1x2x3
+        img_b = torch.rand(batch_size, channels, height, width, device=device, dtype=dtype)
+        img_a = kornia.warp_affine(img_b, aff_ab, (height, width))
+        assert_allclose(img_b, img_a)
+
+    @pytest.mark.parametrize("batch_shape", (
+        [1, 3, 2, 5], [2, 4, 3, 4], [3, 5, 6, 2]
+    ))
+    @pytest.mark.parametrize("out_shape", (
+        [2, 5], [3, 4], [6, 2]
+    ))
+    def test_cardinality(self, device, dtype, batch_shape, out_shape):
+        batch_size, channels, height, width = batch_shape
+        h_out, w_out = out_shape
+        aff_ab = torch.eye(2, 3, device=device, dtype=dtype).repeat(batch_size, 1, 1)  # Bx2x3
+        img_b = torch.rand(batch_size, channels, height, width, device=device, dtype=dtype)
+        img_a = kornia.warp_affine(img_b, aff_ab, (h_out, w_out))
+        assert img_a.shape == (batch_size, channels, h_out, w_out)
+
+    def test_exception(self, device, dtype):
+        img = torch.rand(1, 2, 3, 4, device=device, dtype=dtype)
+        aff = torch.eye(2, 3, device=device, dtype=dtype)[None]
+        size = (4, 5)
+
+        with pytest.raises(TypeError):
+            assert kornia.warp_affine(0., aff, size)
+
+        with pytest.raises(TypeError):
+            assert kornia.warp_affine(img, 0., size)
+
+        with pytest.raises(ValueError):
+            img = torch.rand(2, 3, 4, device=device, dtype=dtype)
+            assert kornia.warp_affine(img, aff, size)
+
+        with pytest.raises(ValueError):
+            aff = torch.eye(2, 2, device=device, dtype=dtype)[None]
+            assert kornia.warp_affine(img, aff, size)
+
+    def test_translation(self, device, dtype):
+        offset = 1.
+        h, w = 3, 4
+        aff_ab = torch.eye(2, 3, device=device, dtype=dtype)[None]
+        aff_ab[..., -1] += offset
+
+        img_b = torch.arange(float(h * w), device=device, dtype=dtype).view(1, 1, h, w)
+
+        expected = torch.zeros_like(img_b)
+        expected[..., 1:, 1:] = img_b[..., :2, :3]
+
+        # Same as opencv: cv2.warpAffine(kornia.tensor_to_image(img_b), aff_ab[0].numpy(), (w, h))
+        img_a = kornia.warp_affine(img_b, aff_ab, (h, w))
+        assert_allclose(img_a, expected)
+
+    def test_rotation_inverse(self, device, dtype):
+        h, w = 4, 4
+        img_b = torch.rand(1, 1, h, w, device=device, dtype=dtype)
+
+        # create rotation matrix of 90deg (anti-clockwise)
+        center = torch.tensor([[w - 1, h - 1]], device=device, dtype=dtype) / 2
+        scale = torch.ones((1, 2), device=device, dtype=dtype)
+        angle = 90. * torch.ones(1, device=device, dtype=dtype)
+        aff_ab = kornia.get_rotation_matrix2d(center, angle, scale)
+        # Same as opencv: cv2.getRotationMatrix2D(((w-1)/2,(h-1)/2), 90., 1.)
+
+        # warp the tensor
+        # Same as opencv: cv2.warpAffine(kornia.tensor_to_image(img_b), aff_ab[0].numpy(), (w, h))
+        img_a = kornia.warp_affine(img_b, aff_ab, (h, w))
+
+        # invert the transform
+        aff_ba = kornia.convert_affinematrix_to_homography(aff_ab).inverse()[..., :2, :]
+        img_b_hat = kornia.warp_affine(img_a, aff_ba, (h, w))
+        assert_allclose(img_b_hat, img_b)
+
+    def test_jit(self, device, dtype):
+        aff_ab = torch.eye(2, 3, device=device, dtype=dtype)[None]
+        img = torch.rand(1, 2, 3, 4, device=device, dtype=dtype)
+        args = (img, aff_ab, (4, 5))
+        op = kornia.warp_affine
+        op_jit = torch.jit.script(op)
+        assert_allclose(op(*args), op_jit(*args))
+
+    def test_gradcheck(self, device, dtype):
+        batch_size, channels, height, width = 1, 2, 3, 4
+        aff_ab = torch.eye(2, 3, device=device, dtype=dtype)[None] + 1e-6  # 1x2x3
+        img_b = torch.rand(batch_size, channels, height, width, device=device, dtype=dtype)
+        aff_ab = utils.tensor_to_gradcheck_var(aff_ab)  # to var
+        img_b = utils.tensor_to_gradcheck_var(img_b)  # to var
+        assert gradcheck(kornia.warp_affine, (img_b, aff_ab, (height, width),),
+                         raise_exception=True)
+
+
 class TestWarpPerspective:
+    def test_smoke(self, device, dtype):
+        batch_size, channels, height, width = 1, 2, 3, 4
+        img_b = torch.rand(batch_size, channels, height, width, device=device, dtype=dtype)
+        H_ab = kornia.eye_like(3, img_b)
+        img_a = kornia.warp_perspective(img_b, H_ab, (height, width))
+        assert_allclose(img_b, img_a)
+
+    @pytest.mark.parametrize("batch_shape", (
+        [1, 3, 2, 5], [2, 4, 3, 4], [3, 5, 6, 2]
+    ))
+    @pytest.mark.parametrize("out_shape", (
+        [2, 5], [3, 4], [6, 2]
+    ))
+    def test_cardinality(self, device, dtype, batch_shape, out_shape):
+        batch_size, channels, height, width = batch_shape
+        h_out, w_out = out_shape
+        img_b = torch.rand(batch_size, channels, height, width, device=device, dtype=dtype)
+        H_ab = kornia.eye_like(3, img_b)
+        img_a = kornia.warp_perspective(img_b, H_ab, (h_out, w_out))
+        assert img_a.shape == (batch_size, channels, h_out, w_out)
+
+    def test_exception(self, device, dtype):
+        img = torch.rand(1, 2, 3, 4, device=device, dtype=dtype)
+        homo = torch.eye(3, device=device, dtype=dtype)[None]
+        size = (4, 5)
+
+        with pytest.raises(TypeError):
+            assert kornia.warp_perspective(0., homo, size)
+
+        with pytest.raises(TypeError):
+            assert kornia.warp_perspective(img, 0., size)
+
+        with pytest.raises(ValueError):
+            img = torch.rand(2, 3, 4, device=device, dtype=dtype)
+            assert kornia.warp_perspective(img, homo, size)
+
+        with pytest.raises(ValueError):
+            homo = torch.eye(2, 2, device=device, dtype=dtype)[None]
+            assert kornia.warp_perspective(img, homo, size)
+
+    def test_translation(self, device, dtype):
+        offset = 1.
+        h, w = 3, 4
+
+        img_b = torch.arange(float(h * w), device=device, dtype=dtype).view(1, 1, h, w)
+        homo_ab = kornia.eye_like(3, img_b)
+        homo_ab[..., :2, -1] += offset
+
+        expected = torch.zeros_like(img_b)
+        expected[..., 1:, 1:] = img_b[..., :2, :3]
+
+        # Same as opencv: cv2.warpPerspective(kornia.tensor_to_image(img_b), homo_ab[0].numpy(), (w, h))
+        img_a = kornia.warp_perspective(img_b, homo_ab, (h, w))
+        assert_allclose(img_a, expected)
+
+    def test_rotation_inverse(self, device, dtype):
+        h, w = 4, 4
+        img_b = torch.rand(1, 1, h, w, device=device, dtype=dtype)
+
+        # create rotation matrix of 90deg (anti-clockwise)
+        center = torch.tensor([[w - 1, h - 1]], device=device, dtype=dtype) / 2
+        scale = torch.ones((1, 2), device=device, dtype=dtype)
+        angle = 90. * torch.ones(1, device=device, dtype=dtype)
+        aff_ab = kornia.get_rotation_matrix2d(center, angle, scale)
+        # Same as opencv: cv2.getRotationMatrix2D(((w-1)/2,(h-1)/2), 90., 1.)
+
+        H_ab = kornia.convert_affinematrix_to_homography(aff_ab)  # Bx3x3
+
+        # warp the tensor
+        # Same as opencv: cv2.warpPerspecive(kornia.tensor_to_image(img_b), H_ab[0].numpy(), (w, h))
+        img_a = kornia.warp_perspective(img_b, H_ab, (h, w))
+
+        # invert the transform
+        H_ba = torch.inverse(H_ab)
+        img_b_hat = kornia.warp_perspective(img_a, H_ba, (h, w))
+        assert_allclose(img_b_hat, img_b)
+
     @pytest.mark.parametrize("batch_size", [1, 5])
     @pytest.mark.parametrize("channels", [1, 5])
     def test_crop(self, batch_size, channels, device, dtype):
@@ -171,20 +302,12 @@ def test_crop(self, batch_size, channels, device, dtype):
             [13, 14, 15, 16],
         ]]], device=device, dtype=dtype).expand(batch_size, channels, -1, -1)
 
-        expected = torch.tensor(
-            [[[[0.2500, 0.9167, 1.5833],
-               [2.1667, 5.1667, 6.5000],
-               [4.8333, 10.5000, 11.8333]]]], device=device, dtype=dtype).repeat(batch_size, channels, 1, 1)
+        expected = patch[..., :3, :3]
 
         # warp and assert
-        patch_warped = kornia.warp_perspective(patch, dst_trans_src,
-                                               (dst_h, dst_w))
-        assert_allclose(patch_warped, expected, rtol=1e-4, atol=1e-4)
-
-        # check jit
-        patch_warped_jit = kornia.jit.warp_perspective(patch, dst_trans_src,
-                                                       (dst_h, dst_w))
-        assert_allclose(patch_warped, patch_warped_jit, rtol=1e-4, atol=1e-4)
+        patch_warped = kornia.warp_perspective(
+            patch, dst_trans_src, (dst_h, dst_w))
+        assert_allclose(patch_warped, expected)
 
     def test_crop_center_resize(self, device, dtype):
         # generate input data
@@ -219,56 +342,34 @@ def test_crop_center_resize(self, device, dtype):
             [13, 14, 15, 16],
         ]]], device=device, dtype=dtype)
 
-        expected = torch.tensor(
-            [[[[5.1667, 5.6111, 6.0556, 6.5000],
-               [6.9444, 7.3889, 7.8333, 8.2778],
-               [8.7222, 9.1667, 9.6111, 10.0556],
-               [10.5000, 10.9444, 11.3889, 11.8333]]]], device=device, dtype=dtype)
+        expected = torch.tensor([[[
+            [6.0000, 6.3333, 6.6667, 7.0000],
+            [7.3333, 7.6667, 8.0000, 8.3333],
+            [8.6667, 9.0000, 9.3333, 9.6667],
+            [10.0000, 10.3333, 10.6667, 11.0000]]]], device=device, dtype=dtype)
 
         # warp and assert
-        patch_warped = kornia.warp_perspective(patch, dst_trans_src,
-                                               (dst_h, dst_w))
-        assert_allclose(patch_warped, expected, rtol=1e-4, atol=1e-4)
-
-        # check jit
-        patch_warped_jit = kornia.jit.warp_perspective(patch, dst_trans_src,
-                                                       (dst_h, dst_w))
-        assert_allclose(patch_warped, patch_warped_jit, rtol=1e-4, atol=1e-4)
-
-
-class TestWarpAffine:
-    def test_smoke(self, device, dtype):
-        batch_size, channels, height, width = 1, 2, 3, 4
-        aff_ab = torch.eye(2, 3, device=device, dtype=dtype)[None]  # 1x2x3
-        img_b = torch.rand(batch_size, channels, height, width, device=device, dtype=dtype)
-        img_a = kornia.warp_affine(img_b, aff_ab, (height, width))
-        assert img_b.shape == img_a.shape
+        patch_warped = kornia.warp_perspective(
+            patch, dst_trans_src, (dst_h, dst_w))
+        assert_allclose(patch_warped, expected)
 
-    @pytest.mark.parametrize("batch_size", [1, 2, 5])
-    def test_translation(self, batch_size, device, dtype):
-        offset = 1.
-        channels, height, width = 1, 3, 4
-        aff_ab = torch.eye(2, 3, device=device, dtype=dtype).repeat(batch_size, 1, 1)  # Bx2x3
-        aff_ab[..., -1] += offset
-        img_b = torch.arange(float(height * width), device=device, dtype=dtype).view(
-            1, channels, height, width).repeat(batch_size, 1, 1, 1)
-        img_a = kornia.warp_affine(img_b, aff_ab, (height, width), align_corners=True)
-        assert_allclose(img_b[..., :2, :3], img_a[..., 1:, 1:], rtol=1e-4, atol=1e-4)
+    def test_jit(self, device, dtype):
+        img = torch.rand(1, 2, 3, 4, device=device, dtype=dtype)
+        H_ab = kornia.eye_like(3, img)
+        args = (img, H_ab, (4, 5))
+        op = kornia.warp_perspective
+        op_jit = torch.jit.script(op)
+        assert_allclose(op(*args), op_jit(*args))
 
     def test_gradcheck(self, device, dtype):
         batch_size, channels, height, width = 1, 2, 3, 4
-        aff_ab = torch.eye(2, 3, device=device, dtype=dtype)[None]  # 1x2x3
         img_b = torch.rand(batch_size, channels, height, width, device=device, dtype=dtype)
-        aff_ab = utils.tensor_to_gradcheck_var(
-            aff_ab, requires_grad=False)  # to var
+        H_ab = kornia.eye_like(3, img_b)
         img_b = utils.tensor_to_gradcheck_var(img_b)  # to var
-        assert gradcheck(
-            kornia.warp_affine, (
-                img_b,
-                aff_ab,
-                (height, width),
-            ),
-            raise_exception=True)
+        # TODO(dmytro/edgar): firgure out why gradient don't propagate for the tranaform
+        H_ab = utils.tensor_to_gradcheck_var(H_ab, requires_grad=False)  # to var
+        assert gradcheck(kornia.warp_perspective, (img_b, H_ab, (height, width),),
+                         raise_exception=True)
 
 
 class TestRemap:


Code review comments:
diff_hunk: @@ -65,7 +54,7 @@ def warp_perspective(src: torch.Tensor, M: torch.Tensor, dsize: Tuple[int, int],
           'bilinear' | 'nearest'. Default: 'bilinear'.
         border_mode (str): padding mode for outside grid values
           'zeros' | 'border' | 'reflection'. Default: 'zeros'.
-        align_corners(bool): interpolation flag. Default: False.
+        align_corners(bool): interpolation flag. Default: True.
path: kornia/geometry/transform/imgwarp.py
text: @user1:
We should probably do a proper deprecation cycle. That means we do

```python
def warp_perspective(align_corners: Optional[bool] = None):
    if align_corners is None:
        warnings.warn("align_corners will change its default value for kornia>0.6.")
        align_corners = False
```

With this only users that used the default value will see the warning.