Python/PIL affine transformation

前端 未结 4 1942
庸人自扰
庸人自扰 2020-12-08 05:40

This is a basic transform question in PIL. I\'ve tried at least a couple of times in the past few years to implement this correctly and it seems there is something I don\'t

4条回答
  •  佛祖请我去吃肉
    2020-12-08 06:10

    I wanted to expand a bit on the answers by carlosdc and Ruediger Jungbeck, to present a more practical python code solution with a bit of explanation.

    First, it is absolutely true that PIL uses inverse affine transformations, as stated in carlosdc's answer. However, there is no need to use linear algebra to compute the inverse transformation from the original transformation—instead, it can easily be expressed directly. I'll use scaling and rotating an image about its center for the example, as in the code linked to in Ruediger Jungbeck's answer, but it's fairly straightforward to extend this to do e.g. shearing as well.

    Before approaching how to express the inverse affine transformation for scaling and rotating, consider how we'd find the original transformation. As hinted at in Ruediger Jungbeck's answer, the transformation for the combined operation of scaling and rotating is found as the composition of the fundamental operators for scaling an image about the origin and rotating an image about the origin.

    However, since we want to scale and rotate the image about its own center, and the origin (0, 0) is defined by PIL to be the upper left corner of the image, we first need to translate the image such that its center coincides with the origin. After applying the scaling and rotation, we also need to translate the image back in such a way that the new center of the image (it might not be the same as the old center after scaling and rotating) ends up in the center of the image canvas.

    So the original "standard" affine transformation we're after will be the composition of the following fundamental operators:

    1. Find the current center (c_x, c_y) of the image, and translate the image by (-c_x, -c_y), so the center of the image is at the origin (0, 0).

    2. Scale the image about the origin by some scale factor (s_x, s_y).

    3. Rotate the image about the origin by some angle \theta.

    4. Find the new center (t_x, t_y) of the image, and translate the image by (t_x, t_y) so the new center will end up in the center of the image canvas.

    To find the transformation we're after, we first need to know the transformation matrices of the fundamental operators, which are as follows:

    • Translation by (x, y):
    • Scaling by (s_x, s_y):
    • Rotation by \theta:

    Then, our composite transformation can be expressed as:

    which is equal to

    or

    where

    .

    Now, to find the inverse of this composite affine transformation, we just need to calculate the composition of the inverse of each fundamental operator in reverse order. That is, we want to

    1. Translate the image by (-t_x, -t_y)

    2. Rotate the image about the origin by -\theta.

    3. Scale the image about the origin by (1/s_x, 1/s_y).

    4. Translate the image by (c_x, c_y).

    This results in a transformation matrix

    where

    .

    This is exactly the same as the transformation used in the code linked to in Ruediger Jungbeck's answer. It can be made more convenient by reusing the same technique that carlosdc used in their post for calculating (t_x, t_y) of the image, and translate the image by (t_x, t_y)—applying the rotation to all four corners of the image, and then calculating the distance between the minimum and maximum X and Y values. However, since the image is rotated about its own center, there's no need to rotate all four corners, since each pair of oppositely facing corners are rotated "symmetrically".

    Here is a rewritten version of carlosdc's code that has been modified to use the inverse affine transformation directly, and which also adds scaling:

    from PIL import Image
    import math
    
    
    def scale_and_rotate_image(im, sx, sy, deg_ccw):
        im_orig = im
        im = Image.new('RGBA', im_orig.size, (255, 255, 255, 255))
        im.paste(im_orig)
    
        w, h = im.size
        angle = math.radians(-deg_ccw)
    
        cos_theta = math.cos(angle)
        sin_theta = math.sin(angle)
    
        scaled_w, scaled_h = w * sx, h * sy
    
        new_w = int(math.ceil(math.fabs(cos_theta * scaled_w) + math.fabs(sin_theta * scaled_h)))
        new_h = int(math.ceil(math.fabs(sin_theta * scaled_w) + math.fabs(cos_theta * scaled_h)))
    
        cx = w / 2.
        cy = h / 2.
        tx = new_w / 2.
        ty = new_h / 2.
    
        a = cos_theta / sx
        b = sin_theta / sx
        c = cx - tx * a - ty * b
        d = -sin_theta / sy
        e = cos_theta / sy
        f = cy - tx * d - ty * e
    
        return im.transform(
            (new_w, new_h),
            Image.AFFINE,
            (a, b, c, d, e, f),
            resample=Image.BILINEAR
        )
    
    
    im = Image.open('test.jpg')
    im = scale_and_rotate_image(im, 0.8, 1.2, 10)
    im.save('outputpython.png')
    

    and this is what the result looks like (scaled with (sx, sy) = (0.8, 1.2), and rotated 10 degrees counter-clockwise):

提交回复
热议问题