상세 컨텐츠

본문 제목

PIL rotate

Coding/Image

by linguana 2021. 6. 22. 14:35

본문

  PIL의 rotate 함수에 대해서 알아보자.

1. 예시... 예시를 보자!

  For intuition, let's quickly take a look at a 간단하게 돌려본 결과:
  (1) 이미지 열고
  (2) 이미지 크기 조정 좀 해주고 (너무 컸음)
  (3) 10도 만큼 회전해줌.

PIL rotate example


 2. 기능 설명 (어디 쓰는 물건이고?)

  주 의: PIL의 rotate는 중앙을 기준으로 반시계방향으로 돌린다.

  arguments 설명:
  (1) resample: 회전하고 나서 채우는 전략 NEAREST(default), BILINEAR, BICUBIC 있음.
  (2) expand: 0이면 원본 이미지 크기 유지하는데, 0 이외의 값이면 (int 참 값) 돌리고 난 다음의 크기로 채워줌
  (3) center: None이면 이미지 중앙을 기준으로 돌림. 형식: (x, y) 같은 튜플
  (4) translate: None이면 평행이동 하지 않음. 형식: (x, y) 같은 튜플
  (5) fillcolor: 회전하고 남은 외부 부분 채울 색.

rotate 함수

  프로젝트 목적 상, expand만 신경쓰면 됨.
  나머지는 default 값으로 두고 expand만 int True로 둘 생각임.


3. 소스 코드 분석

  소스 코드 [1] 좀 길이가 길어서 접은 글에 고이 넣어드림 ㅎ.

더보기
# source code from PIL rotate
# https://pillow.readthedocs.io/en/stable/_modules/PIL/Image.html#Image.rotate
    def rotate(
        self,
        angle,
        resample=NEAREST,
        expand=0,
        center=None,
        translate=None,
        fillcolor=None,
    ):
        """
        Returns a rotated copy of this image.  This method returns a
        copy of this image, rotated the given number of degrees counter
        clockwise around its centre.

        :param angle: In degrees counter clockwise.
        :param resample: An optional resampling filter.  This can be
           one of :py:data:`PIL.Image.NEAREST` (use nearest neighbour),
           :py:data:`PIL.Image.BILINEAR` (linear interpolation in a 2x2
           environment), or :py:data:`PIL.Image.BICUBIC`
           (cubic spline interpolation in a 4x4 environment).
           If omitted, or if the image has mode "1" or "P", it is
           set to :py:data:`PIL.Image.NEAREST`. See :ref:`concept-filters`.
        :param expand: Optional expansion flag.  If true, expands the output
           image to make it large enough to hold the entire rotated image.
           If false or omitted, make the output image the same size as the
           input image.  Note that the expand flag assumes rotation around
           the center and no translation.
        :param center: Optional center of rotation (a 2-tuple).  Origin is
           the upper left corner.  Default is the center of the image.
        :param translate: An optional post-rotate translation (a 2-tuple).
        :param fillcolor: An optional color for area outside the rotated image.
        :returns: An :py:class:`~PIL.Image.Image` object.
        """

        angle = angle % 360.0

        # Fast paths regardless of filter, as long as we're not
        # translating or changing the center.
        if not (center or translate):
            if angle == 0:
                return self.copy()
            if angle == 180:
                return self.transpose(ROTATE_180)
            if angle == 90 and expand:
                return self.transpose(ROTATE_90)
            if angle == 270 and expand:
                return self.transpose(ROTATE_270)

        # Calculate the affine matrix.  Note that this is the reverse
        # transformation (from destination image to source) because we
        # want to interpolate the (discrete) destination pixel from
        # the local area around the (floating) source pixel.

        # The matrix we actually want (note that it operates from the right):
        # (1, 0, tx)   (1, 0, cx)   ( cos a, sin a, 0)   (1, 0, -cx)
        # (0, 1, ty) * (0, 1, cy) * (-sin a, cos a, 0) * (0, 1, -cy)
        # (0, 0,  1)   (0, 0,  1)   (     0,     0, 1)   (0, 0,   1)

        # The reverse matrix is thus:
        # (1, 0, cx)   ( cos -a, sin -a, 0)   (1, 0, -cx)   (1, 0, -tx)
        # (0, 1, cy) * (-sin -a, cos -a, 0) * (0, 1, -cy) * (0, 1, -ty)
        # (0, 0,  1)   (      0,      0, 1)   (0, 0,   1)   (0, 0,   1)

        # In any case, the final translation may be updated at the end to
        # compensate for the expand flag.

        w, h = self.size

        if translate is None:
            post_trans = (0, 0)
        else:
            post_trans = translate
        if center is None:
            # FIXME These should be rounded to ints?
            rotn_center = (w / 2.0, h / 2.0)
        else:
            rotn_center = center

        angle = -math.radians(angle)
        matrix = [
            round(math.cos(angle), 15),
            round(math.sin(angle), 15),
            0.0,
            round(-math.sin(angle), 15),
            round(math.cos(angle), 15),
            0.0,
        ]

        def transform(x, y, matrix):
            (a, b, c, d, e, f) = matrix
            return a * x + b * y + c, d * x + e * y + f

        matrix[2], matrix[5] = transform(
            -rotn_center[0] - post_trans[0], -rotn_center[1] - post_trans[1], matrix
        )
        matrix[2] += rotn_center[0]
        matrix[5] += rotn_center[1]

        if expand:
            # calculate output size
            xx = []
            yy = []
            for x, y in ((0, 0), (w, 0), (w, h), (0, h)):
                x, y = transform(x, y, matrix)
                xx.append(x)
                yy.append(y)
            nw = math.ceil(max(xx)) - math.floor(min(xx))
            nh = math.ceil(max(yy)) - math.floor(min(yy))

            # We multiply a translation matrix from the right.  Because of its
            # special form, this is the same as taking the image of the
            # translation vector as new translation vector.
            matrix[2], matrix[5] = transform(-(nw - w) / 2.0, -(nh - h) / 2.0, matrix)
            w, h = nw, nh

        return self.transform((w, h), AFFINE, matrix, resample, fillcolor=fillcolor)

 

  엑기스를 추출해보자. 다음과 같은 조치를 취하였다.
  - 웬만한 optional argument 무시 (expand, center, translate, fillcolor)
  - AFFINE, resample=NEAREST 같은 값은 PIL 라이브러리에서 이미 정의된 __transformer() 함수에서 요구하기 때문에 정의를 해주어야 함. 그렇지 않으면 다음과 같은 에러를 보게 됨: (아래 예시는 resample 인자를 missing한 경우)

PIL __transformer(), 이미지에 transform 메소드 쓰면 기존에 정의된 함수를 따라야 함

  - rotate의 인자 중 self 였던 것을 image로 바꿈
  - center=None, translate=None으로 가정하기 때문에 해당하는 값 rotn_center, post_trans을 정의해줌.

 

# Simplified rotate function
import math
AFFINE = 0
NEAREST = NONE = 0

def rotate(image, angle):
  # self --> image
  angle = angle % 360.0
  w, h = image.size

  # if center is None:
  rotn_center = (w / 2.0, h / 2.0)
  # if translate in None:
  post_trans = (0, 0)

  angle = -math.radians(angle)
  matrix = [
            round(math.cos(angle), 15),
            round(math.sin(angle), 15),
            0.0,
            round(-math.sin(angle), 15),
            round(math.cos(angle), 15),
            0.0,
          ]

  def transform(x, y, matrix):
    (a, b, c, d, e, f) = matrix
    return a * x + b * y + c, d * x + e * y + f

  matrix[2], matrix[5] = transform(
      -rotn_center[0] - post_trans[0],
      -rotn_center[1] - post_trans[1],
      matrix
    )
  matrix[2] += rotn_center[0]
  matrix[5] += rotn_center[1]
  
  return image.transform((w, h), AFFINE, matrix, resample=NEAREST)
  
  # 위에서 사용한 sample image를 계속해서 사용한다고 했을 때
  # rotate(sample, 10)

결과 이미지:

simplified rotate function

친절한 설명은 불가! 똑똑한 수학적 원리를 이용해서 회전을 시키는구나 싶다.

  math.radians()를 사용하는 이유는 Side note (2)를 보자.
  matrix 부분을 이해하려면 Side note (3)과 위키백과 "회전변환행렬"을 보자.

  핵심은 회전변환행렬이다. [3]의 pg. 2-4를 보도록 하자.

출처: 위키백과


Side note:
(1) round 함수: ndigits 소수점 만큼 round해줌.

round 함수

(2) Radian 라디안 사용하는 이유 [2]:

  차원이 없는 값(dimensionless)으로 사용하기 위해서 rad 사용함. (각도에 대해서 계산하기 용이하게 함)

  rad: 호(l)와 반지름(r)의 비율; l / r

 

위키백과 gif:

radian gif

(3) Affine Matrix [3]:

affines-matrices.pdf
3.39MB

 


Reference

[1] https://pillow.readthedocs.io/en/stable/_modules/PIL/Image.html#Image.rotate (PIL 공식문서)
[2] 호도법 (radian) 을 쓰는 이유 - 수험생 물리 (physicstutor.kr)
[3] https://people.cs.clemson.edu/~dhouse/courses/401/notes/affines-matrices.pdf#:~:text=The%20affine%20transforms%20scale%2C%20rotate%20and%20shear%20are,%3B%20orx0%3D%20Mx%2C%20where%20M%20is%20the%20matrix. 

[4] 7장 - 삼각함수 : 라디안 : 네이버 블로그 (naver.com) ([2]보다 친절하게 설명해주진 않지만 야매 느낌으로 직관적임)

'Coding > Image' 카테고리의 다른 글

KakaoBrain Pororo OCR  (0) 2021.06.23
Custom Text Image Generator  (0) 2021.06.22
FCN (Fully Convolutional Network)  (0) 2021.06.16
CTPN  (0) 2021.06.15
YOLO PyTorch Implementation  (0) 2021.06.14

관련글 더보기