안경잡이개발자

728x90
반응형

  Rejection Sampling은 특정한 확률 분포에서 데이터를 샘플링하기 위해(표본을 뽑기 위해) 효과적으로 사용할 수 있는 알고리즘 중 하나다. 단, Rejection Sampling을 하기 위해서는 특정한 확률 분포의 확률 밀도 함수(PDF)를 알고 있어야 한다. 예를 들어 우리가 주사위를 굴린다고 하면, 각 경우에 대하여 확률이 다음과 같이 구성된다는 것을 알 수 있다.

 

  1) 눈금 1이 나올 확률 = 1/6

  2) 눈금 2가 나올 확률 = 1/6

  3) 눈금 3이 나올 확률 = 1/6

  4) 눈금 4가 나올 확률 = 1/6

  5) 눈금 5가 나올 확률 = 1/6

  6) 눈금 6이 나올 확률 = 1/6

 

  하지만 우리가 "주사위"를 갖고 있지 않아서, 확률 분포에서 샘플링(sampling)을 진행할 수 없는 상황이라고 가정해보자. 반면에 우리에게 주사위 대신 "동전"이 있다고 해보자. 우리가 동전을 던지면, 각 경우에 대하여 확률이 다음과 같이 구성된다.

 

  1) 앞면이 나올 확률 = 1/2

  2) 뒷면이 나올 확률 = 1/2

 

  이때 "동전"을 가지고 "주사위"를 던지는 것과 동일한 시행을 할 수 있을까? 즉, 우리는 확률 분포 p(주사위 굴리기)에서는 샘플링을 수행할 수 없고, 확률 분포 q(동전 던지기)에서는 직접적으로 샘플링을 수행할 수 있는 상황이다. 이때 사용할 수 있는 것이 바로 Rejection Sampling이다. Rejection Sampling을 이용한 방식은 다음과 같다.

 

  1. 동전을 세 번  던져서 [0, 7] 사이의 값을 만든다.

    1) 만약 값이 6 혹은 7이라면, reject하고 다시 sampling한다.

    2) 만약 값이 [0, 5] 사이의 값이라면 거기에 1을 더해 반환하면 된다.

 

  이를 코드로 옮긴 것은 다음과 같다. 동전 던지기를 수행하는 coin() 메서드를 이용해, 결과적으로 100번의 주사위 굴리기를 수행하여 결과를 출력한다.

 

import random


def coin():
    return random.randint(0, 1)


def three():
    out = coin()
    out = out * 2 + coin()
    out = out * 2 + coin()
    return out


def dice():
    res = three()
    while res >= 6:
        res = three()
    return res + 1


for i in range(100):
    print('Dice:', dice())
728x90
반응형

Comment +0

728x90
반응형

※ 예약 전 확인할 내용 ※

 

  이번 코로나 백신은 10부제로 예약이 진행되기 때문에, 미리 예약 날짜를 확인할 필요가 있다. 필자의 경우 18세~49세 예방접종 대상자에 해당한다. 주민등록번호 앞자리를 확인하여 적절한 요일에 예약을 진행하면 된다. 예를 들어 961222라면, 생년월일 끝자리가 2에 해당한다.

 

 

  필자의 경우 생년월일이 961222이기 때문에, 8월 12일(목요일)에 코로나19 접종 예약을 진행하면 되는 상황이었다. 사전 예약은 오후 8시부터 시작된다. 그래서 저녁을 먹은 뒤에 조금 기다렸다가 예약을 진행하면 된다.

 

※ 코로나19 예방접종 사전예약하는 방법 ※

 

  코로나19 예방접종 사전예약 시스템에 접속하여 예약을 진행할 수 있다.

 

  ▶ 코로나19 예방접종 사전예약: https://ncvr2.kdca.go.kr/

 

코로나19 예방접종 사전예약 시스템

코로나19 예방접종 사전예약 시스템

ncvr2.kdca.go.kr

 

  사전예약은 오후 8시에 진행되는데, 8시가 되기 직전에 웹 사이트에 접속하면 다음과 같은 화면이 나온다.

 

 

  오후 8시가 되는 순간 새로 고침을 진행하면, 다음과 같이 화면이 나온다. 버튼에 체크하고, [확인] 버튼을 눌러서 예약을 진행할 수 있다.

 

 

  이후에 다음과 같이 [본인 인증 방법]을 선택한다.

 

 

  참고로 접속자가 많아서 렉이 걸릴 때는 다음과 같이 하얀색 그림만 뜨는 경우가 있다. 필자는 마지막 버튼이 [휴대폰 인증]인 것을 알고 있기 때문에 이것을 선택하였다. 하지만, 이렇게 진행해도 다음으로 넘어가지 않는 것을 확인했다. 그래서 크롬(Chrome) 말고 웨일(Whale) 브라우저를 이용했는데, 그랬더니 정상적으로 그림이 출력되어서 그걸 이용해 진행했다. 따라서 혹시나 다음과 같이 하얀색 그림만 뜨는 경우에는, 다른 브라우저를 이용해 보거나 스마트 폰 앱을 이용하는 방법을 추천한다.

 

 

  아무튼 페이지가 먹통 상태가 아니라 정상적으로 접속이 된다면, 다음과 같이 휴대폰 인증을 진행할 수 있다. 필자의 경우 휴대폰 인증을 선택했는데, 생각보다 매우 간편했다. 지체없이 빠르게 진행되었다.

 

 

  휴대폰 인증을 마치면 다음과 같이 실제 예약 페이지가 나온다. 여기에서 [예약 정보]를 입력한다. 필자의 경우 병원을 선택할 때 필자가 지내는 곳에서 가까운 곳을 선택했다.

 

 

  결과적으로 내용을 모두 입력한 뒤에, [의료기관 찾기]를 눌러서 자신의 집과 가까운 의료기관을 선택하면 다음과 같이 된다. 필자는 [카카오톡]을 통해 백신 관련 내용을 전달받도록 했다.

 

 

  결과적으로 다음과 같이 [예약완료 안내] 페이지를 확인할 수 있다.

 

728x90
반응형

Comment +0

728x90
반응형

  학술 대회나 학술지에 논문을 투고하기 전에 Google Scholar 계정을 요구하는 경우가 있다. 필자는 아직까지 Google Scholar 계정이 없는 상태였는데, 이번에 한 컨퍼런스에 논문을 투고하는 김에 Google Scholar에  Google Scholar에 회원가입을 진행하는 방법은 간단하다. 아래의 Google Scholar 프로필 페이지에 방문한다.

 

  ▶ Google Scholar 프로필: https://scholar.google.com/citations

 

Google 학술검색 서지정보

하나의 계정으로 모든 Google 서비스를 Google 학술검색 서지정보로 이동하려면 로그인하세요.

accounts.google.com

 

  프로필 페이지에 방문한 뒤에 다음과 같이 이름, 소속(학교나 연구실), 이메일, 관심 분야를 입력하면 된다. 필자 또한 필자의 프로필 정보를 입력했다.

 

 

  이후에 [학술자료]를 선택하는 페이지가 등장한다. 기존에 저술한 논문이 있으면, 다음과 같이 자동으로 검색되어 노출된다.

 

 

  이후에 다음과 같이 [설정] 페이지에서는 기본 설정을 진행할 수 있다. 필자는 학술자료 [업데이트 자동 적용] 버튼을 눌렀다.

 

 

  이후에 다음과 같이 인증 메일이 도착하는데 [이메일 주소 확인] 버튼을 누른다.

 

 

  이후에 프로필 사진을 등록하면 다음과 같이 Google Scholar 프로필 설정이 완료된다.

 

728x90
반응형

Comment +0

728x90
반응형

  하나의 동영상/사진 위에 다른 동영상을 올려서 합성하고자 할 때 크로마키 합성 기법을 이용할 수 있다. 크로마키 합성 방법은 매우 간단하므로, 빠르게 실습해보겠다. 일단 크로마키 천과 함께 찍은 영상을 준비해 보자. 일반적으로 크로마키 천과 함께 찍은 영상이라고 해도, 다음과 같이 배경 사물이 존재할 수 있다.

 

 

  이럴 때는 [효과 컨트롤]에서 [불투명도]에서 [펜] 도구를 이용하여 인물이 존재하는 영역만 얻을 수 있다.

 


  결과적으로 다음과 같이 자신이 원하는 인물이 존재하는 영역만 잘라낼 수 있다.

 

 

  이제 크로마키 합성을 위해 [효과] 탭에서 [울트라 키]를 찾아 적용한다.

 

 

  이후에 [키 색상] 속성의 값을 설정한다. 스포이드 도구를 이용하여 크로마키 천의 색상을 선택한다. 그러면 크로마키 천의 색상이랑 같은 요소들의 픽셀이 제거된다.

 

 

  구체적으로, 필자의 경우 다음과 같은 초록색을 선택했다.

 

 

  이제 다음과 같이 배경 사진/동영상 위에 크로마키 합성이 들어가는 것을 알 수 있다.

 

 

  이제 위치를 적절하게 옮겨보자. 배경과 올바르게 섞였는지 확인할 수 있다. 다만 크로마키 천이 완전히 제거되지 않아서, 어색한 느낌이 나는 것을 알 수 있다.

 

 

  이럴 때는 [페데스탈] 속성의 값을 높여보는 것을 추천한다. 

 

 

  그러면 결과적으로 사람을 제외한 크로마키 천 배경 부분에 대해서는 대부분의 픽셀이 제거되어 적절하게 영상 내에 크로마키 합성이 적용되는 것을 알 수 있다.

 

 

  필자는 다음과 같이 천사와 악마 사이에서 고민하는 컨셉을 위해 크로마키 합성 기법을 활용했다.

 

728x90
반응형

Comment +0

728x90
반응형

  FAIR (Facebook AI Research) 팀에서 만든 fvcore를 이용하여 NumPy 형식의 이미지변형(transformation)할 수 있다. 다음과 같이 fvcore를 설치할 수 있으며, 사용 방법 또한 매우 간단하다.

 

!pip install fvcore

 

  실습을 위해 간단히 한 장의 고양이 이미지를 준비해 보자. 필자는 상업적으로 사용이 가능한 무료 이미지를 준비해 보았다.

 

!curl https://visualhunt.com/photos/16/cat.jpg -o cat.jpg

 

 

import cv2

np_image = cv2.imread('cat.jpg')
print(np_image.shape)

 

  실행 결과를 확인해 보면 (696, 1024, 3)이라는 값이 이미지 해상도로 출력된다. 이후에 필요한 라이브러리를 불러와 보자. 기본적으로 랜덤 변형(Random Transformation)은 다음과 같이 fvcore의 다양한 Transform 함수를 활용한다. 그래서 일단 필요한 라이브러리를 모두 불러올 수 있도록 하자.

 

import numpy as np
import matplotlib.pyplot as plt

from fvcore.transforms.transform import (
    BlendTransform,
    CropTransform,
    HFlipTransform,
    NoOpTransform,
    PadTransform,
    Transform,
    TransformList,
    VFlipTransform,
)

 

1. Random Contrast

 

  랜덤으로 대비(contrast)를 변경할 수 있다. 파라미터로 intensity_min과 intensity_max를 넣을 수 있는데, 값이 1일 때는 변화를 주지 않겠다는 것이다. 필자의 경우 [0.8, 1.2] 정도의 옵션으로 많이 사용했다.

 

class RandomContrast():
    """
    Randomly transforms image contrast.
    Contrast intensity is uniformly sampled in (intensity_min, intensity_max).
    - intensity < 1 will reduce contrast
    - intensity = 1 will preserve the input image
    - intensity > 1 will increase contrast
    See: https://pillow.readthedocs.io/en/3.0.x/reference/ImageEnhance.html
    """

    def __init__(self, intensity_min, intensity_max):
        """
        Args:
            intensity_min (float): Minimum augmentation
            intensity_max (float): Maximum augmentation
        """
        self.intensity_min = intensity_min
        self.intensity_max = intensity_max

    def get_transform(self, image):
        w = np.random.uniform(self.intensity_min, self.intensity_max)
        return BlendTransform(src_image=image.mean(), src_weight=1 - w, dst_weight=w)

 

  RandomContrast는 다음과 같이 사용하면 된다.

 

random_contrast = RandomContrast(intensity_min=0.8, intensity_max=1.2)
transform = random_contrast.get_transform(np_image)
print(transform)
plt.imshow(transform.apply_image(np_image)[...,[2,1,0]])

 

  대비 값이 1.2일 때(intensity_min=1.2, intensity_max=1.2)는 다음과 같다.

 

 

  대비 값이 0.8일 때intensity_min=0.8, intensity_max=0.8)는 다음과 같다.

 

 

2. Random Brightness

 

  랜덤으로 명도(brightness)를 변경할 수 있다. 파라미터로 intensity_min과 intensity_max를 넣을 수 있는데, 값이 1일 때는 변화를 주지 않겠다는 것이다. 필자의 경우 [0.8, 1.2] 정도의 옵션으로 많이 사용했다.

 

class RandomBrightness():
    """
    Randomly transforms image brightness.
    Brightness intensity is uniformly sampled in (intensity_min, intensity_max).
    - intensity < 1 will reduce brightness
    - intensity = 1 will preserve the input image
    - intensity > 1 will increase brightness
    See: https://pillow.readthedocs.io/en/3.0.x/reference/ImageEnhance.html
    """

    def __init__(self, intensity_min, intensity_max):
        """
        Args:
            intensity_min (float): Minimum augmentation
            intensity_max (float): Maximum augmentation
        """
        self.intensity_min = intensity_min
        self.intensity_max = intensity_max

    def get_transform(self, image):
        w = np.random.uniform(self.intensity_min, self.intensity_max)
        return BlendTransform(src_image=0, src_weight=1 - w, dst_weight=w)

 

  RandomBrightness는 다음과 같이 사용하면 된다.

 

random_brightness = RandomBrightness(intensity_min=0.8, intensity_max=1.2)
transform = random_brightness.get_transform(np_image)
print(transform)
plt.imshow(transform.apply_image(np_image)[...,[2,1,0]])

 

  명도 값이 1.2일 때(intensity_min=1.2, intensity_max=1.2)는 다음과 같다.

 

 

  명도 값이 0.8일 때(intensity_min=0.8, intensity_max=0.8)는 다음과 같다.

 

 

3. Random Flip

 

  랜덤으로 뒤집기(flip)를 수행할 수 있다. 아래 코드의 경우 기본 설정(default setting)상 horizontal flip만을 사용한다. 일반적으로 computer vision에서 사물을 분류(classification)하는 작업(task)의 경우 vertical flip으로 인해 오히려 성능이 떨어지는 경우도 있는 반면에, horizontal flip은 대체로 성능상의 향상을 이끌어 낸다는 특징이 있다. 또한 prob는 flip을 수행할 확률에 해당한다.

 

class RandomFlip():
    """
    Flip the image horizontally or vertically with the given probability.
    """

    def __init__(self, prob=0.5, *, horizontal=True, vertical=False):
        """
        Args:
            prob (float): probability of flip.
            horizontal (boolean): whether to apply horizontal flipping
            vertical (boolean): whether to apply vertical flipping
        """
        if horizontal and vertical:
            raise ValueError("Cannot do both horiz and vert. Please use two Flip instead.")
        if not horizontal and not vertical:
            raise ValueError("At least one of horiz or vert has to be True!")

        self.prob = prob
        self.horizontal = horizontal
        self.vertical = vertical

    def _rand_range(self, low=1.0, high=None, size=None):
        """
        Uniform float random number between low and high.
        """
        if high is None:
            low, high = 0, low
        if size is None:
            size = []
        return np.random.uniform(low, high, size)

    def get_transform(self, image):
        h, w = image.shape[:2]
        do = self._rand_range() < self.prob
        if do:
            if self.horizontal:
                return HFlipTransform(w)
            elif self.vertical:
                return VFlipTransform(h)
        else:
            return NoOpTransform()

 

  다음과 같이 사용하면 된다.

 

random_flip = RandomFlip(prob=0.5, horizontal=True, vertical=False)
transform = random_flip.get_transform(np_image)
print(transform)
plt.imshow(transform.apply_image(np_image)[...,[2,1,0]])

 

  좌우 반전(horizontal flip) 결과는 다음과 같다. (prob=1.0, horizontal=True, vertical=False)

 

 

  상하 반전(vertical flip) 결과는 다음과 같다. (prob=1.0, horizontal=False, vertical=True)

 

 

4. Random Saturation

 

  랜덤으로 채도(saturation)를 변경할 수 있다. 파라미터로 intensity_min과 intensity_max를 넣을 수 있는데, 값이 1일 때는 변화를 주지 않겠다는 것이다. 필자의 경우 [0.8, 1.2] 정도의 옵션으로 많이 사용했다.

 

class RandomSaturation():
    """
    Randomly transforms saturation of an RGB image.
    Input images are assumed to have 'RGB' channel order.
    Saturation intensity is uniformly sampled in (intensity_min, intensity_max).
    - intensity < 1 will reduce saturation (make the image more grayscale)
    - intensity = 1 will preserve the input image
    - intensity > 1 will increase saturation
    See: https://pillow.readthedocs.io/en/3.0.x/reference/ImageEnhance.html
    """

    def __init__(self, intensity_min, intensity_max):
        """
        Args:
            intensity_min (float): Minimum augmentation (1 preserves input).
            intensity_max (float): Maximum augmentation (1 preserves input).
        """
        self.intensity_min = intensity_min
        self.intensity_max = intensity_max

    def get_transform(self, image):
        assert image.shape[-1] == 3, "RandomSaturation only works on RGB images"
        w = np.random.uniform(self.intensity_min, self.intensity_max)
        grayscale = image.dot([0.299, 0.587, 0.114])[:, :, np.newaxis]
        return BlendTransform(src_image=grayscale, src_weight=1 - w, dst_weight=w)

 

  다음과 같이 사용하면 된다.

 

random_saturation = RandomSaturation(intensity_min=0.8, intensity_max=1.2)
transform = random_saturation.get_transform(np_image)
print(transform)
plt.imshow(transform.apply_image(np_image)[...,[2,1,0]])

 

  채도 값이 0.8일 때(intensity_min=0.8, intensity_max=0.8)는 다음과 같다.

 

 

  채도 값이 1.2일 때(intensity_min=1.2, intensity_max=1.2)는 다음과 같다.

 

 

5. Random Rotation

 

  랜덤으로 회전(rotation)을 수행할 수 있다. angle 변수에 (min, max) 값을 넣어 회전을 수행할 수 있다. 필자의 경우 (-10, 10) 정도의 옵션으로 많이 사용했다.

 

class RotationTransform(Transform):
    """
    This method returns a copy of this image, rotated the given
    number of degrees counter clockwise around its center.
    """

    def __init__(self, h, w, angle, expand=True, center=None, interp=None):
        """
        Args:
            h, w (int): original image size
            angle (float): degrees for rotation
            expand (bool): choose if the image should be resized to fit the whole
                rotated image (default), or simply cropped
            center (tuple (width, height)): coordinates of the rotation center
                if left to None, the center will be fit to the center of each image
                center has no effect if expand=True because it only affects shifting
            interp: cv2 interpolation method, default cv2.INTER_LINEAR
        """
        super().__init__()
        image_center = np.array((w / 2, h / 2))
        if center is None:
            center = image_center
        if interp is None:
            interp = cv2.INTER_LINEAR
        abs_cos, abs_sin = (abs(np.cos(np.deg2rad(angle))), abs(np.sin(np.deg2rad(angle))))
        if expand:
            # find the new width and height bounds
            bound_w, bound_h = np.rint(
                [h * abs_sin + w * abs_cos, h * abs_cos + w * abs_sin]
            ).astype(int)
        else:
            bound_w, bound_h = w, h

        self._set_attributes(locals())
        self.rm_coords = self.create_rotation_matrix()
        # Needed because of this problem https://github.com/opencv/opencv/issues/11784
        self.rm_image = self.create_rotation_matrix(offset=-0.5)

    def apply_image(self, img, interp=None):
        """
        img should be a numpy array, formatted as Height * Width * Nchannels
        """
        if len(img) == 0 or self.angle % 360 == 0:
            return img
        assert img.shape[:2] == (self.h, self.w)
        interp = interp if interp is not None else self.interp
        return cv2.warpAffine(img, self.rm_image, (self.bound_w, self.bound_h), flags=interp)

    def apply_coords(self, coords):
        """
        coords should be a N * 2 array-like, containing N couples of (x, y) points
        """
        coords = np.asarray(coords, dtype=float)
        if len(coords) == 0 or self.angle % 360 == 0:
            return coords
        return cv2.transform(coords[:, np.newaxis, :], self.rm_coords)[:, 0, :]

    def apply_segmentation(self, segmentation):
        segmentation = self.apply_image(segmentation, interp=cv2.INTER_NEAREST)
        return segmentation

    def create_rotation_matrix(self, offset=0):
        center = (self.center[0] + offset, self.center[1] + offset)
        rm = cv2.getRotationMatrix2D(tuple(center), self.angle, 1)
        if self.expand:
            # Find the coordinates of the center of rotation in the new image
            # The only point for which we know the future coordinates is the center of the image
            rot_im_center = cv2.transform(self.image_center[None, None, :] + offset, rm)[0, 0, :]
            new_center = np.array([self.bound_w / 2, self.bound_h / 2]) + offset - rot_im_center
            # shift the rotation center to the new coordinates
            rm[:, 2] += new_center
        return rm

    def inverse(self):
        """
        The inverse is to rotate it back with expand, and crop to get the original shape.
        """
        if not self.expand:  # Not possible to inverse if a part of the image is lost
            raise NotImplementedError()
        rotation = RotationTransform(
            self.bound_h, self.bound_w, -self.angle, True, None, self.interp
        )
        crop = CropTransform(
            (rotation.bound_w - self.w) // 2, (rotation.bound_h - self.h) // 2, self.w, self.h
        )
        return TransformList([rotation, crop])


class RandomRotation():
    """
    This method returns a copy of this image, rotated the given
    number of degrees counter clockwise around the given center.
    """

    def __init__(self, angle, expand=True, center=None, sample_style="range", interp=None):
        """
        Args:
            angle (list[float]): If ``sample_style=="range"``,
                a [min, max] interval from which to sample the angle (in degrees).
                If ``sample_style=="choice"``, a list of angles to sample from
            expand (bool): choose if the image should be resized to fit the whole
                rotated image (default), or simply cropped
            center (list[[float, float]]):  If ``sample_style=="range"``,
                a [[minx, miny], [maxx, maxy]] relative interval from which to sample the center,
                [0, 0] being the top left of the image and [1, 1] the bottom right.
                If ``sample_style=="choice"``, a list of centers to sample from
                Default: None, which means that the center of rotation is the center of the image
                center has no effect if expand=True because it only affects shifting
        """
        super().__init__()
        assert sample_style in ["range", "choice"], sample_style
        self.is_range = sample_style == "range"
        if isinstance(angle, (float, int)):
            angle = (angle, angle)
        if center is not None and isinstance(center[0], (float, int)):
            center = (center, center)
        self.angle = angle
        self.expand = expand
        self.center = center
        self.sample_style = sample_style
        self.interp = interp

    def get_transform(self, image):
        h, w = image.shape[:2]
        center = None
        if self.is_range:
            angle = np.random.uniform(self.angle[0], self.angle[1])
            if self.center is not None:
                center = (
                    np.random.uniform(self.center[0][0], self.center[1][0]),
                    np.random.uniform(self.center[0][1], self.center[1][1]),
                )
        else:
            angle = np.random.choice(self.angle)
            if self.center is not None:
                center = np.random.choice(self.center)

        if center is not None:
            center = (w * center[0], h * center[1])  # Convert to absolute coordinates

        if angle % 360 == 0:
            return NoOpTransform()

        return RotationTransform(h, w, angle, expand=self.expand, center=center, interp=self.interp)

 

  다음과 같이 사용하면 된다. 만약에 expand를 사용하고 싶지 않다면 expand의 값을 False로 넣으면 된다.

 

random_rotation = RandomRotation(angle=(10, 10))
transform = random_rotation.get_transform(np_image)
print(transform)
plt.imshow(transform.apply_image(np_image)[...,[2,1,0]])

 

  angle 값이 10일 때(angle=[10, 10])는 다음과 같다.

 

 

  angle 값이 10일 때(angle=[10, 10], expand=False)의 또 다른 예시는 다음과 같다. 이 경우 회전만 시키고, 원래의 이미지 사이즈에 맞게 이미지를 resize하지 않기 때문에, 사진의 일부 영역이 가려져 보이지 않게 된다.

 

 

6. Resize Shortest Edge

 

  참고로 랜덤(random) 변형은 아니지만, computer vision task에서 많이 사용되는 augmentation 기법 중 하나로 resize shortest edge가 있다. 이것은 이미지의 가로 혹은 세로 중에서 짧은 변의 길이가 특정한 값이 되도록 이미지를 resize하는 기법이다.

 

import sys
from PIL import Image


class ResizeTransform(Transform):
    """
    Resize the image to a target size.
    """

    def __init__(self, h, w, new_h, new_w, interp=None):
        """
        Args:
            h, w (int): original image size
            new_h, new_w (int): new image size
            interp: PIL interpolation methods, defaults to bilinear.
        """
        # TODO decide on PIL vs opencv
        super().__init__()
        if interp is None:
            interp = Image.BILINEAR
        self._set_attributes(locals())

    def apply_image(self, img, interp=None):
        assert img.shape[:2] == (self.h, self.w)
        assert len(img.shape) <= 4
        interp_method = interp if interp is not None else self.interp

        if img.dtype == np.uint8:
            if len(img.shape) > 2 and img.shape[2] == 1:
                pil_image = Image.fromarray(img[:, :, 0], mode="L")
            else:
                pil_image = Image.fromarray(img)
            pil_image = pil_image.resize((self.new_w, self.new_h), interp_method)
            ret = np.asarray(pil_image)
            if len(img.shape) > 2 and img.shape[2] == 1:
                ret = np.expand_dims(ret, -1)
        else:
            # PIL only supports uint8
            if any(x < 0 for x in img.strides):
                img = np.ascontiguousarray(img)
            img = torch.from_numpy(img)
            shape = list(img.shape)
            shape_4d = shape[:2] + [1] * (4 - len(shape)) + shape[2:]
            img = img.view(shape_4d).permute(2, 3, 0, 1)  # hw(c) -> nchw
            _PIL_RESIZE_TO_INTERPOLATE_MODE = {
                Image.NEAREST: "nearest",
                Image.BILINEAR: "bilinear",
                Image.BICUBIC: "bicubic",
            }
            mode = _PIL_RESIZE_TO_INTERPOLATE_MODE[interp_method]
            align_corners = None if mode == "nearest" else False
            img = F.interpolate(
                img, (self.new_h, self.new_w), mode=mode, align_corners=align_corners
            )
            shape[:2] = (self.new_h, self.new_w)
            ret = img.permute(2, 3, 0, 1).view(shape).numpy()  # nchw -> hw(c)

        return ret

    def apply_coords(self, coords):
        coords[:, 0] = coords[:, 0] * (self.new_w * 1.0 / self.w)
        coords[:, 1] = coords[:, 1] * (self.new_h * 1.0 / self.h)
        return coords

    def apply_segmentation(self, segmentation):
        segmentation = self.apply_image(segmentation, interp=Image.NEAREST)
        return segmentation

    def inverse(self):
        return ResizeTransform(self.new_h, self.new_w, self.h, self.w, self.interp)


class ResizeShortestEdge():
    """
    Scale the shorter edge to the given size, with a limit of `max_size` on the longer edge.
    If `max_size` is reached, then downscale so that the longer edge does not exceed max_size.
    """

    def __init__(
        self, short_edge_length, max_size=sys.maxsize, sample_style="range", interp=Image.BILINEAR
    ):
        """
        Args:
            short_edge_length (list[int]): If ``sample_style=="range"``,
                a [min, max] interval from which to sample the shortest edge length.
                If ``sample_style=="choice"``, a list of shortest edge lengths to sample from.
            max_size (int): maximum allowed longest edge length.
            sample_style (str): either "range" or "choice".
        """
        super().__init__()
        assert sample_style in ["range", "choice"], sample_style

        self.is_range = sample_style == "range"
        if isinstance(short_edge_length, int):
            short_edge_length = (short_edge_length, short_edge_length)
        if self.is_range:
            assert len(short_edge_length) == 2, (
                "short_edge_length must be two values using 'range' sample style."
                f" Got {short_edge_length}!"
            )
        self.short_edge_length = short_edge_length
        self.max_size = max_size
        self.sample_style = sample_style
        self.interp = interp

    def get_transform(self, image):
        h, w = image.shape[:2]
        if self.is_range:
            size = np.random.randint(self.short_edge_length[0], self.short_edge_length[1] + 1)
        else:
            size = np.random.choice(self.short_edge_length)
        if size == 0:
            return NoOpTransform()

        scale = size * 1.0 / min(h, w)
        if h < w:
            newh, neww = size, scale * w
        else:
            newh, neww = scale * h, size
        if max(newh, neww) > self.max_size:
            scale = self.max_size * 1.0 / max(newh, neww)
            newh = newh * scale
            neww = neww * scale
        neww = int(neww + 0.5)
        newh = int(newh + 0.5)
        return ResizeTransform(h, w, newh, neww, self.interp)

 

  min_size = 400, max_size = 4,000일 때의 예시는 다음과 같다. 짧은 변(세로)의 길이가 400이 된다.

 

resize_shortest_edge = ResizeShortestEdge(400, 4000)
transform = resize_shortest_edge.get_transform(np_image)
print(transform)
plt.imshow(transform.apply_image(np_image)[...,[2,1,0]])

 

 

  min_size = 700, max_size = 4,000일 때의 예시는 다음과 같다. 짧은 변(세로)의 길이가 700이 된다.

 

 

  min_size = 3600, max_size = 4,000일 때의 예시는 다음과 같다. 긴 변(가로)의 길이가 최대 4,000이어야 하기 때문에, 짧은 변(세로)의 길이가 2719까지만 증가하고 멈추게 된다.

 

728x90
반응형

Comment +0

728x90
반응형

  TestDome은 코딩 테스트 서비스 회사 중 하나다. 해외 및 국내에서 많은 기업이 TestDome 사이트를 이용해 지원자들을 코딩 테스트를 통해 평가하고 있다.

 

  ▶ TestDome 테스트(Test) 사이트https://www.testdome.com/tests

 

Tests | TestDome

 

www.testdome.com

 

  사이트에 접속하면 다음과 같이 다양한 프로그래밍 테스트 유형의 문제들을 확인할 수 있다. 한 번 간단하게 [Python Algorithms and SQL] 페이지로 접속해 보겠다.

 

 

  자신이 원하는 테스트 유형을 선택한 뒤에, [Take a Practice Test] 버튼을 눌러 연습용 테스트를 진행할 수 있다.

 

 

  TestDome에서는 기본적으로 인터넷 검색 및 기타 IDE를 사용하는 것을 허용한다. 필자가 지원했던 회사에서도 TestDome 사이트를 이용해 코딩 테스트를 치르도록 했는데, 인터넷 검색을 허용한다고 적혀 있었다. 문제 예시를 확인하기 위해 간단히 [Try sample question] 버튼을 누르면 된다.

 

 

  참고로 코드는 코드 입력 창으로 붙여넣기가 가능하다. [Run] 버튼을 눌러 작성한 코드를 실행할 수 있는데, 각 테스트 케이스에 따라서 정답 여부가 출력된다. 또한, 코드 작성이 완료되면 [Submit] 버튼을 눌러 최종 정답(final answer)으로 제출할 수 있다. 만약 시간이 아직 남아 있다면, 마지막에 제출한 정답이 최종 정답이 된다. 참고로, 제한 시간이 얼마 안 남았을 때 스피커를 통해 몇 분 남았다고 안내 메시지가 소리로 출력된다.

 

  아래는 샘플 문제(sample question)TestDome이 제공하는 문제 예시다. 문제에서 요구하는 대로 코드를 작성한 뒤에 채점을 받을 수 있다. 제한 시간이 끝나면 알아서 종료된다.

 

 

  샘플 문제를 풀어 본 뒤에는, 약관에 동의하고 [Start the test] 버튼을 누르면 실제 회사 입사 코딩 테스트를 보는 것처럼 테스트를 볼 수 있다. 자신이 지원하는 직군에 맞는 테스트로 한 번 문제를 풀어 보는 것을 추천한다.

 

728x90
반응형

Comment +0

728x90
반응형

  FiftyOne 라이브러리는 COCO 데이터셋을 관리하기에 매우 편리한 라이브러리 중 하나입니다. FiftyOne 라이브러리의 공식 웹 사이트에서 제공하고 있는 COCO 데이터셋 관리 튜토리얼은 다음과 같습니다. 본 포스팅은 아래의 튜토리얼을 참고하여 작성되었습니다.

 

  ▶ FiftyOne 공식 웹 사이트: https://voxel51.com/docs/fiftyone/integrations/coco.html

 

COCO Integration — FiftyOne 0.11.1 documentation

COCO Integration With support from the team behind the COCO dataset, we’ve made it easy to dowload, visualize, and evaluate on the COCO dataset natively in FiftyOne! Note Check out this tutorial to see how you can use FiftyOne to evaluate a model on COCO

voxel51.com

 

※ FiftyOne 라이브러리 설치 ※

 

  FiftyOne 라이브러리는 다음과 같은 명령어로 설치를 진행할 수 있습니다.

 

!pip install fiftyone

 

※ COCO 2017 데이터셋에서 200개의 데이터만 가지고 오기 ※

 

  COCO 2017 validation 데이터셋에서 특정 개수의 이미지만 가지고 오고 싶다면 어떻게 하면 될까요? 예를 들어 200개의 이미지만 가지고 오고 싶다면, 다음과 같이 하면 됩니다.

 

import fiftyone as fo
import fiftyone.zoo as foz


dataset = foz.load_zoo_dataset(
    "coco-2017",
    split="validation",
    max_samples=200,
    shuffle=True,
)

 

  그러면 자동으로 서버에서 annotations 데이터와 이미지 200개를 다운로드하여 가져오게 됩니다. 참고로 COCO 2017의 validation 데이터셋은 총 5,000개의 이미지로 구성되어 있기 때문에, 만약에 max_samples의 값으로 5,000 이상의 값을 넣으면 전체 이미지 개수인 5,000장까지만 다운로드가 진행됩니다.

 

※ dataset 객체를 다시 파일로 저장하기 ※

 

  그렇다면 fiftyone.core.dataset 객체에 포함된 이미지를 어떻게 다시 폴더로 내보내기할 수 있을까요? 한 번 [링크] dataset 객체 설명 페이지에 접속해 봅시다. 페이지에 접속하여 export() 함수에 대한 설명을 확인할 수 있습니다. 설명을 확인해 보면 export_dir에 이미지 파일들이 저장되고, labels_path에 레이블에 대한 정보가 담기게 됩니다.

 

dataset.export(
    export_dir='./my_coco_val2017_200/',
    dataset_type=fo.types.COCODetectionDataset,
    label_field="ground_truth",
    labels_path="./annotations/instances.json",
)

 

  실제로 위 코드를 실행하면 다음과 같은 형태로 이미지와 레이블 정보가 기록됩니다.

 

 

  이러한 폴더를 압축하여 내보내고 싶다면 다음과 같이 할 수 있습니다.

 

!zip -r my_coco_val2017_200.zip ./my_coco_val2017_200/*

 

※ (참고) COCO 형식의 폴더로부터 데이터셋 불러오기 ※

 

  COCO 형식의 데이터셋 폴더로부터 dataset 객체를 불러오는 방법을 확인해 봅시다. 이 또한 굉장히 간단합니다. 자세한 내용은 [링크] FiftyOne 디스크로부터 데이터셋 불러오기 튜토리얼 페이지에서 확인할 수 있습니다.

 

# 이미지를 포함하고 있는 폴더 경로
data_path = "/path/to/images"

# COCO 레이블 JSON 파일이 포함된 경로
labels_path = "/path/to/coco-labels.json"

# 데이터셋 불러오기
dataset = fo.Dataset.from_dir(
    dataset_type=fo.types.COCODetectionDataset,
    data_path=data_path,
    labels_path=labels_path,
)
728x90
반응형

Comment +0

728x90
반응형

  원드라이브(OneDrive)를 사용하고 있는 회사나 학교가 많다. 필자 또한 학교에서 메일 서비스와 저장소 서비스를 Microsoft의 OneDrive를 통해 제공하고 있다. 필자를 포함해 많은 학교에서는 기본적으로 1TB 정도의 용량을 제공하는 경우가 많다. 이때 할당된 용량 중에서 얼마나 남았는지 궁금하다면, 다음과 같이 확인할 수 있다.

 

  [오른쪽 위 톱니바퀴(설정) 버튼] - [OneDrive 설정] - [기타 설정] - [저장소 메트릭]에 들어간다.

 

 

  그러면 다음과 같이 저장소의 남은 공간이 출력된다. 필자의 경우 1TB 중에서 627.25GB의 공간이 남은 상태다.

 

 

728x90
반응형

Comment +0

728x90
반응형

  COCO 데이터셋은 객체 탐지 (object detection), 세그먼테이션 (segmentation), 키포인트 탐지 (keypoint detection) 등의 컴퓨터 비전(computer vision) 분야의 task를 목적으로 만들어진 데이터셋입니다. 실제로 object detection 관련 논문을 읽어 보면, 논문에서 성능 평가 목적으로 많이 사용되는 데이터셋 중에서 COCO 2017을 확인할 수 있습니다. 더불어 많은 object detection 라이브러리는 이러한 COCO 데이터셋으로 사전 학습된(pre-trained) 모델을 제공하고 있습니다. COCO 2017 데이터셋은 다음과 같이 구성되어 있습니다.

 

  ▶ 학습(training) 데이터셋: 118,000장의 이미지

  ▶ 검증(validation) 데이터셋: 5,000장의 이미지

  ▶ 테스트(test) 데이터셋: 41,000장의 이미지

 

  COCO 데이터셋은 다음의 경로에서 자세한 내용을 확인할 수 있습니다.

 

  ▶ COCO 데이터셋 공식 웹 사이트: https://cocodataset.org/

 

COCO - Common Objects in Context

 

cocodataset.org

 

  다음과 같이 [데이터셋(dataset)] 페이지로 이동한 뒤에, 데이터셋을 다운로드할 수 있습니다. 저는 간단히 사전 학습된 모델에 대한 평가(evaluation)만 진행할 것이기 때문에, 2017 validation 데이터셋Train/Val annotations 파일을 다운로드하였습니다.

 

 

  해당 파일을 다운로드해 보면, 2017 validation 데이터셋에는 다음과 같이 5,000장의 이미지가 존재하는 것을 확인할 수 있습니다. 전체 용량은 약 800MB 정도입니다.

 

 

  또한 annotations 파일을 다운로드하면, 다음과 같이 captions_val2017.json 파일이 존재합니다. 이 파일은 이미지에 대한 기본적인 설명이 포함되어 있습니다. 그리고 instances_val2017.json 파일에는 각 이미지 파일 내부에 어떠한 객체(object)가 존재하는지, 바운딩 박스(bounding box)와 함께 그 클래스(class) 정보가 함께 주어집니다.

 

 

  따라서 instances.json 파일이 일반적인 세그먼테이션(segmentation), 객체 탐지(object Detection) 분야에서 가장 중요한 annotation 파일입니다. 실제로 내용을 열어 보시면, 다음과 같이 각 이미지(image)에 대하여 파일명이 무엇인지, 이미지의 높이와 너비는 어느 정도인지에 대한 이미지 파일 관련 정보가 나와 있습니다.

 

"images": [
    {
        "license": 4,
        "file_name": "000000397133.jpg",
        "coco_url": "http://images.cocodataset.org/val2017/000000397133.jpg",
        "height": 427,
        "width": 640,
        "date_captured": "2013-11-14 17:02:52",
        "flickr_url": "http://farm7.staticflickr.com/6116/6255196340_da26cf2c9e_z.jpg",
        "id": 397133
    },
    {
        "license": 1,
        "file_name": "000000037777.jpg",
        "coco_url": "http://images.cocodataset.org/val2017/000000037777.jpg",
        "height": 230,
        "width": 352,
        "date_captured": "2013-11-14 20:55:31",
        "flickr_url": "http://farm9.staticflickr.com/8429/7839199426_f6d48aa585_z.jpg",
        "id": 37777
     },
     ...
],

 

  이후에 annotations 정보에 대한 상세 내용을 확인할 수 있습니다. 여기에서는 각 사물(object)에 대하여 segmentation 정보가 들어가 있습니다. 해당 사물이 이미지 내 어떤 위치에 있는지, 구체적인 바운딩 박스(bounding box)에 대한 정보와 함께 기록되어 있습니다.

 

"annotations": [
    {
        "segmentation": [
            [
                510.66,
                423.01,
                511.72,
                420.03,
                510.45,
                416.0,
                510.34,
                413.02,
                ...
            ]
        ],
        "area": 702.1057499999998,
        "iscrowd": 0,
        "image_id": 289343,
        "bbox": [
            473.07,
            395.93,
            38.65,
            28.67
        ],
        "category_id": 18,
        "id": 1768
    },
    {
        "segmentation": [
            [
                289.74,
                443.39,
                302.29,
                445.32,
                308.09,
                427.94,
                310.02,
                416.35,
                ...
            ]
        ],
        "area": 27718.476299999995,
        "iscrowd": 0,
        "image_id": 61471,
        "bbox": [
            272.1,
            200.23,
            151.97,
            279.77
        ],
        "category_id": 18,
        "id": 1773
    },
    ...
]
728x90
반응형

Comment +0

728x90
반응형

  카카오 뱅크 공모주 청약 일정이 나왔습니다. 공모가는 39,000원이고, 청약일은 7월 26일(월요일)과 7월 27일(화요일)입니다. 또한, 상장일은 8월 6일(금요일)이라고 합니다. 투자자 입장에서 공모주는 상장 당일 매도함으로써 수익실현을 하기 위해 구매하는 경우가 많습니다. 또한 기본적으로 카카오 뱅크 공모주 청약은 중복 청약이 안 되는 것으로 알려져 있습니다. 그래서 KB증권, 한국투자증권, 하나금융투자, 현대차증권 중에서 하나를 선택해 청약을 진행하면 됩니다.

 

  이 중에서 많은 공모 청약 물량을 갖고 있는 증권사 중 하나인 한국투자증권을 이용해 청약을 진행하는 방법을 소개합니다. 한국투자증권을 이용해 공모주 청약을 진행하려면, 먼저 한국투자증권 주식 계좌가 있어야 합니다. 혹시나 한국투자증권 주식 계좌가 없으신 분들을 위해, 비대면(온라인)으로 계좌를 만드는 방법을 소개하자면 다음과 같습니다.

 

  ▶ 한국투자증권 모바일 앱(App)을 이용하여 주식(증권) 계좌 개설하는 방법

 

  이제 청약을 진행해 봅시다. 한국투자증권 모바일 앱을 실행하여 [계좌/서비스] - [청약] - [공모주/실권주 청약 신청/취소] 페이지로 이동합니다. 이후에 [청약 신청] 버튼을 눌러 청약 가능 종목을 확인할 수 있는데, 카카오뱅크가 나오는 것을 알 수 있습니다. 따라서 카카오뱅크를 선택하여 청약 신청을 진행하면 됩니다.

 

 

  참고로 청약 신청 전에 미리 [주식 계좌]에 돈을 입금해 놓으면 됩니다. 필자의 경우 600만 원을 주식 계좌에 입금해 놓았습니다. 청약 신청을 위해 [투자설명서]를 확인한 뒤에, 자신이 구매하고자 하는 청약 수량을 입력하면 됩니다. 필자의 경우 300주를 입력했고, 이에 따른 청약 증거금은 5,850,000원입니다. 필자의 주식 계좌에 있는 돈이 약 600만 원이므로, 청약 신청이 가능합니다.

 

 

  결과적으로 다음과 같이 [청약하기] 버튼을 눌러 공모주 청약을 신청 완료할 수 있습니다. 참고로 저녁 시간에는 신청이 불가능할 수 있습니다.

 

728x90
반응형

Comment +0