如何消除 OpenCV WarpAffine 旋转中的黑色像素行

How to eliminate row of black pixels from OpenCV WarpAffine rotation

提问人:Cary H 提问时间:8/4/2020 最后编辑:Cary H 更新时间:6/17/2021 访问量:958

问:

我使用 cv2.warpAffine() 将 3 张图像旋转 180 度,然后用 cv2.hconcat() 水平连接它们。这是在图像之间添加一个 1 像素宽的黑色列,但 img.shape 中的图像宽度是正确的。如果我不旋转它们,图像看起来不错,没有黑色列。所有 3 张图像均为 1920 宽 x 1200 高。

如何消除黑柱?它类似于 - warpAffine

这不会发生在 Scipy 身上。注释掉的代码 (ndimage.rotate()) 是我用 Scipy 解决它的方式 - 从这里开始。Scipy 代码较慢,我有数千张图像。

编辑

一分钟后,我现在使用 numpy 将矩阵旋转 90 度两次。来自 numpy.rot90() 这似乎更快。它也在下面的注释代码中。对于非 90 度角,我将坚持使用 opencv 的 warpAffine。

import cv2
import numpy as np
from scipy import ndimage


def rotate_image(mat, angle):     
    """   Rotates an image (angle in degrees) and expands image to avoid cropping
    """
    height, width = mat.shape[:2] # image shape has 3 dimensions
    image_center = (width/2, height/2) # getRotationMatrix2D needs coordinates in reverse order (width, height) compared to shape

    rotation_mat = cv2.getRotationMatrix2D(image_center, angle, 1.0000)

    # rotation calculates the cos and sin, taking absolutes of those.
    abs_cos = abs(rotation_mat[0,0]) 
    abs_sin = abs(rotation_mat[0,1])

    # find the new width and height bounds
    bound_w = int(height * abs_sin + width * abs_cos)
 
    bound_h = int(height * abs_cos + width * abs_sin)
    
   
    # find the new width and height bounds
    bound_w = int(height * abs_sin + width * abs_cos)    
    bound_h = int(height * abs_cos + width * abs_sin)
    print(f'Bounds w = {bound_w} Bound H = {bound_h}')
    # subtract old image center (bringing image back to original) and adding the new image center coordinates
    rotation_mat[0, 2] += bound_w/2 - image_center[0]
    rotation_mat[1, 2] += bound_h/2 - image_center[1]
  

    # rotate image with the new bounds and translated rotation matrix
    rotated_mat = cv2.warpAffine(mat, rotation_mat, (bound_w, bound_h))
    return rotated_mat

left_img = cv2.imread(r"F:\Basler\1595525164.242553_l.tiff",0)
cent_img = cv2.imread(r"F:\Basler\1595525164.242553_c.tiff",0)
rigt_img = cv2.imread(r"F:\Basler\1595525164.242553_r.tiff",0)
print(f'Shape = {rigt_img.shape} is {len(rigt_img.shape)}')

angle = 180


left_rot = rotate_image(left_img, angle)
cent_rot = rotate_image(cent_img, angle)
rigt_rot = rotate_image(cent_img, angle)
'''
left_rot = ndimage.rotate(left_img, angle)
cent_rot = ndimage.rotate(cent_img, angle)
rigt_rot = ndimage.rotate(rigt_img, angle)

THIS SEEMS THE FASTEST
left_rot = np.rot90(left_img,2)
cent_rot = np.rot90(cent_img,2)
rigt_rot = np.rot90(rigt_img,2)
'''
#lane_img = np.concatenate((left_rot, cent_rot, rigt_rot), axis=1)
lane_img = cv2.hconcat([left_rot, cent_rot, rigt_rot])
print(f'Size = {lane_img.shape}')
cv2.imwrite(r'C:\Users\Cary\Desktop\Junk\lane1.tiff', lane_img)
python-3.x opencv 图像处理

评论

0赞 dpetrini 8/4/2020
我现在无法运行您的代码,但是在您控制它们之前,它们是否有不需要的栏?
0赞 Karson 8/4/2020
为什么您可以简单地镜像图像 180 度?根据旋转功能的实现方式,它可能比镜像慢,而不是无损。
0赞 dpetrini 8/11/2020
您好@Cary H,您能测试下面的答案吗?
0赞 Cary H 8/14/2020
它们在旋转时有不需要的杆。图像尺寸为 1920 x 1200 ,前后。我还没有测试边框添加。我现在会。此外,镜像和旋转是大不相同的 Karson。

答:

0赞 dpetrini 8/4/2020 #1

可以通过在旋转之前使用 copyMakeBorder 在图像的每一侧添加一条额外的线来删除该行:

after_mat = cv2.copyMakeBorder(
        mat,
        top=1,
        bottom=1,
        left=1,
        right=1,
        borderType=cv2.BORDER_REFLECT
    )

# rotate image with the new bounds and translated rotation matrix
rotated_mat = cv2.warpAffine(after_mat, rotation_mat, (bound_w, bound_h))

我不知道额外行的原因(也许是由于旋转而引起的偏移?),但上面的代码可以抑制它,希望没有副作用。

评论

0赞 Cary H 6/19/2021
这可能有效,但我认为@aerobiomat有正确的旋转方式。
1赞 aerobiomat 6/17/2021 #2

对于 90 度的倍数旋转,使用 numpy.rot90() 或 numpy.flip() 总是更快、更安全。

然而,该函数在许多图像旋转配方中都存在一个常见的错误。rotate_image()

问题在于图像中心的计算。想象一下 3 列乘以 2 行的小图像。您的代码使用:

>>> rows = 2
>>> cols = 3
>>> cols/2, rows/2
(1.5, 1.0)

但是列是 (0, 1, 2),所以中心列必须是 1,行是 (0, 1),所以中心“行”必须是 0.5:

>>> (cols-1)/2, (rows-1)/2
(1.0, 0.5)

在以下图像上使用原始代码:

>>> rows, cols = 10, 15
>>> left_img = np.full((rows, cols), 200, dtype=np.uint8)
>>> cent_img = np.full((rows, cols), 150, dtype=np.uint8)
>>> rigt_img = np.full((rows, cols), 100, dtype=np.uint8)

您将获得:

Extra black bars

这是您的代码,使用正确的中心计算进行了更新:

def rotate_image(mat, angle):

    height, width = mat.shape[:2]
    image_center = (width - 1) / 2, (height - 1) / 2     # <<<=========

    rotation_mat = cv2.getRotationMatrix2D(image_center, angle, 1.0000)

    abs_cos = abs(rotation_mat[0, 0])
    abs_sin = abs(rotation_mat[0, 1])

    bound_w = int(height * abs_sin + width * abs_cos)
    bound_h = int(height * abs_cos + width * abs_sin)

    new_center = (bound_w - 1) / 2, (bound_h - 1) / 2    # <<<=========
    rotation_mat[0, 2] += new_center[0] - image_center[0]
    rotation_mat[1, 2] += new_center[1] - image_center[1]

    rotated_mat = cv2.warpAffine(mat, rotation_mat, (bound_w, bound_h))
    return rotated_mat

应用于相同的图像,现在您将获得:

No black bars

当角度不是 90 度的倍数时,这个问题在大图像中不太明显,但问题仍然存在。以下是旋转 45 度的原始代码示例:

45 degrees wrong

使用适当的中心计算,相对的角实际上是对称的:

45 degrees ok