20/06/05 ~ 07

6/7

인프런의 딥러닝 컴퓨터 비전 완벽 가이드를 수강하기 시작했다. 구글클라우드와 putty를 이용한다. 2개에 대해 자세히는 다루지 않지만 간단하게 사용해볼 수 있어서 좋은것 같다. 오늘은 object detection에서 사용되는 sliding window, selective search, 측정 평가에 사용되는 IOU, Confidence score, mAP에 대해 공부했다. 나중에 시간이 되면 이 부분에 대해 정리해서 올려야 겠다.
캐글 스터디는 팀원들이 nlp 위주로 해서 아쉽지만 그만두게 되었다. 지금은 nlp에 시간을 소요하는 것 보다 영상처리에 대한 기본적인 지식을 습득해야 할 때라고 판단했다. 현재 취업이 된 상태면 지속했을 텐데 아쉽다.
공부가 예정보다 빨라지면 혼자서 영상분야 관련 캐글을 풀어봐야 겠다. 역시 데이터 전처리 부분은 경험적으로 실력이 올라가는 것 같다.

TID


  • 6/5
  • 백준 1939,1637 풀이. 코드는 스터디 repo에 업로드

  • 6/6
  • kaggle_Real or Not? NLP with Disaster Tweets _ 데이터 전처리 나머지 부분
    • 허프변환 정리 및 구현
  • 6/7
  • 인프런의 딥러닝 컴퓨터 비전 완벽 가이드 강의 수강 시작 섹션 0~1까지 수강완료.
    • 기업 분석 연습

허프 변환 (Hough tramsform)

코드링크

허프 변환

에지 세션화를 사용한 방법에서는 비교적 잘 연결할 수 있는 상황을 가정한다.
하지만 현실에서는 연결 관계가 명확하지 않거나 잡음으로 인해 작은 조각을 끊어져 있는 경우도 종종 마주한다.
허프는 이러한 상황에서 연결 과정 없이 바로 직선을 찾아내는 허프 변환(Hough Transform)을 고안했다.
에지 세션화 방식은 어떤 화소의 이웃을 조사하는 지역 연산(Local operation)임에 비해, 허프 변환은 전체 공간을 조사하는 전역 연산(Global operation)이다. ㄸ한 사람이 일직선 상에 있다고 지각하는 점들을 한 곳으로 모으는 원리를 사용해 일종의 지각 군집화(Perceptual grouping)라고 볼 수 있다.

허프변환의 원리는 직선의 방정식을 찾아내는 것이다. 기울기와 y절편이 축인 공간에서는 기울기가 무한대인 상황이 있기 때문에 다음의 식을 직선의 방정식으로 삼는다.

\[ycos(\theta)+xsin(\theta)=\rho\]

이 식의 그래프는 아래와 같다.

또 하나 고려해야 할 점은 세 점이 완벽하게 동일한 직선 위에 놓여 있다고 가정했지만 이산 공간에서는 어느 정도의 오류는 필연적으로 발생한다. 허프 변환은 오류를 견디기 위해 구간을 양자화 한다. \(\theta\)와 \(\rho\)가 가질 수 있는 범위는 각각 \(-90^{\circ} \le \theta \le 90^{\circ}\)와 \(-D \le \rho \le D\)이다.
D는 이미지의 좌측 상단에서 우측 하단까지의 거리이다.


코드 구현

다음과 같이 세 점이 있다고 가정했다.

import numpy as np
import matplotlib.pyplot as plt

test_im = np.zeros((8,8))
test_im[1,6]=255
test_im[2,4]=255
test_im[4,1]=255
plt.imshow(test_im,cmap='gray')
plt.show()

theta의 범위는 -90에서 90으로 하고, D는 다음과 같이 구한다. 그리고 양자화는 D의 int값으로 설정했다.

def cal_D_max(img):
    return np.sqrt((len(img)-1)**2 + (len(img[0])-1)**2)

theta_min_max=[-90,90]
D = cal_D_max(test_im)
quenti=[int(D),int(D)]
rho_min_max=[D*-1,D]

각 값의 최소값에 -1을 곱한 값이 offset 값이 되고 현재 값에서 양자화 하는 단위로 나누면 양자화된 index를 구할 수 있다. 마지막 부분은 원래의 그림에서 직선 부분은 빨갛게 칠하는 부분이다.
현재 theta-rho 공간에서 최대값이 3이기 때문에 임계값을 3으로 설정했다.

def hough(canny_img, quenti,theta_range,rho_range,T):
    def cal_rho(theta,y,x):
        theta = theta/180*np.pi
        return y*np.cos(theta)+x*np.sin(theta)
    def cal_y(x,theta,rho):
        theta = theta/180*np.pi
        return (rho-x*np.sin(theta))/np.cos(theta)

    rho_offset = rho_range[0]*-1
    theta_offset = theta_range[0]*-1
    rho_step = (rho_range[1] - rho_range[0]) / quenti[0]
    theta_step = (theta_range[1] - theta_range[0]) / quenti[1]
    start_theta = (theta_range[0]*2+theta_step)/2
    theta_list = np.arange(start_theta,theta_range[1],theta_step)
    start_rho = (rho_range[0]*2+rho_step)/2
    rho_list = np.arange(start_rho, rho_range[1], rho_step)

    A = np.zeros(quenti,np.uint8)
    hough_img=np.zeros([canny_img.shape[0],canny_img.shape[1],3])
    for y in range(len(canny_img)):
        for x in range(len(canny_img[0])):
            if canny_img[y,x]:
                for i in theta_list:
                    rh = cal_rho(i,y,x)
                    if rh>=rho_range[0] and rh<rho_range[1]:
                        n_x =(i+theta_offset)//theta_step
                        n_y = (rh+rho_offset)//rho_step
                        A[int(n_y),int(n_x)]+=1
    A = A>=T

    for rho in range(len(A)):
        for theta in range(len(A[0])):
            if A[rho,theta]:
                for x in range(len(canny_img[0])):
                    y = cal_y(x,theta_list[theta],rho_list[rho])
                    if y>=0 and y<len(canny_img)-0.5:
                        hough_img[int(y+0.5),x,0]=1

    hough_img[..., 1] = np.where(hough_img[...,0]>0,0,canny_img)
    hough_img[..., 2] = np.where(hough_img[...,0] > 0, 0, canny_img)
    hough_img[..., 0] = np.where(hough_img[..., 0] > 0, 255, canny_img)
    return np.uint8(hough_img)

결과는 다음과 같다.

hough_img = hough(test_im,quenti,theta_min_max,rho_min_max,3)
plt.imshow(hough_img,cmap='gray')
plt.show()

기대값(Expected value)

기대값

\(X(\omega)\)에 대해 전체 면적을 1로 두고 다음과 같이 표현해보자.

X를 높이로 두면 다음과 같이 3차원으로 표현된다.

기대값은 평균적으로 기대하는 값을 뜻한다. 이 값은 위 그림의 부피와 같다고 생각하면 된다. \(기대값 E \left[X \right]= (높이 1) \times (바닥 면적 \frac{1}{2}) + (높이2) \times (바닥면적 \frac{1}{3}) +(높이 5) \times (바닥면적 \frac{1}{6}) \\ = 1 \cdot P(X=1)+2 \cdot P(X=2) + 5 \cdot P(X=5) =2\)

기대값의 성질

기대값은 다음과 같은 성질을 가진다.

  • \(E[X]=\sum_{k} kP(X=k)\).
  • \(E[g(X)]=\sum_{k} g(k)P(X=k)\).

이를 해석하면 다음과 같다.

’\(\Omega\) 위의 각 점 \(\omega\)에 대해 \(g(X(\omega))\)를 높이 축에 찍어서 그래프를 그리며, 그렇게 만들어진 오브제(object)의 부피가 기대값 \(E[g(x)]\) 이다.’

20/06/01 ~ 04

6/1

LOG 책에 있는 내용과 OpenCV에서 실제로 동작하는게 조금 차이가 있는 것 같다.
구현 방식이 제각각이라 우선 OpenCV 내용을 제외 했다.
canny가 좀 더 자주 사용되는 것으로 알고 있는데 공부하다가 LOG가 필요하면 그때 다시 정리해야 겠다.

6/3~6/4

여태 구현할때 오른쪽이랑 왼쪽을 바꿔서 구현했다는 것을 깨달았다.

이부분 다 수정하고 에지 세션화 구현 중인데 중간에 잘못이해해서 오래 걸렸다. 다신 이런 실수 하지 않도록 주의 해야 겠다.

kaggle을 하루에 1시간 정도만 봤는데 nlp쪽 지식도 별로 없을 뿐더러 kaggle 자체가 타이타닉만 한 수준이라 seaborn도 낯설고 padas 다루는 것도 낯설다.

TID


  • 6/1

    • 영교차 검출 이론 정리 및 구현

    • 이상엽 math 7강

    • 프로그래머스_타겟넘버

  • 6/2

    • canny 연산자 정리 및 구현
    • 프로그래머스_N으로 표현하기
  • 6/3

    • 백준 14503
    • 에지 세션화 정리 및 구현
    • kaggle_Real or Not? NLP with Disaster Tweets 데이터 read 부분
  • 6/4

    • 백준 2602, 1107 - 1107은 이전에 이미 풀어서 정리만 다시
    • 에지 추적 구현 - 이 부분은 구현하고나서 제대로 되어있는지 애매해서 검색했는데 자료가 많이 없다.
    • kaggle_Real or Not? NLP with Disaster Tweets_ meta data plot 부분

에지 segment

코드링크

선분 검출

어떤 응용에서는 이전에 구현한 에지 맵을 사용하면 되는 경우가 있지만, 이웃한 에지 화소를 연결해 에지 토막(edge segment)으로 만들어 응용하는 경우가 대부분이다. 게다가 에지 토막을 직선으로 근사화하여 선분(직선 토막(line segment))으로 변환해야 하는 응용도 많다.
여기서는 edge segment를 구현한다.


세션화

세션화 과정에서는 기존 에지맵의 에지의 두께를 1로 바꿔준다.
여기서는 비교적 간단하고 성능이 좋은 SPTA(Safe Point Thinning Algorithms)를 설명한다.
SPTA알고리즘은 아래 그림의 3x3 마스크를 현재 조사하고 있는 에지 화소 p에 씌운다.

마스크의 0은 비에지, 1은 에지를 뜻하고 x는 0과 1 어느 것이라도 좋다는 기호이다. 이 네 개의 마스크는 하나의 그룹을 형성하는데, 위 그림은 화소 p의 이웃 n_4가 0인 그룹이다.
더 효율적으로 계산하기 위해 네 개의 마스크 대신 논리식 s4를 검사해도 같은 결과를 얻는다. ‘는 부정, +는 or, dot은 and이다.

이 과정을 한번 적용하면 바깥쪽에서 한 화소 두께를 벗기는 셈이 되는데, 원래 SPTA는 더 이상 변화가 없을 때까지 반복하지만 보통 두께는 2~3이므로 두 번만 적용해도 충분하다.


세션화 코드구현

이전에 작성된 코드는 util에 저장하였다.링크

import numpy as np
import cv2
import matplotlib.pyplot as plt
from util import *


img = cv2.imread('./data/food.jpg',cv2.IMREAD_GRAYSCALE)

plt.imshow(img,cmap='gray')
plt.show()

gau_img = gaussian_blur(img,1)
canny_img = canny(gau_img,30,75)


fig = plt.figure()
plt.subplot(121)
plt.imshow(img,cmap='gray')
plt.xlabel('original')

plt.subplot(122)
plt.imshow(canny_img,cmap='gray')
plt.xlabel('original')
fig.tight_layout()
plt.show()

spta는 위의 식을 참고하여 작성했다.

def SPTA(img):
    '''
    n5 n6 n7
    n4 p  n0
    n3 n2 n1
    :param img:
    :return:
    '''
    img = img>0
    n4 = np.pad(img[:, :-1], ((0, 0), (1, 0)))
    n0 = np.pad(img[:, 1:], ((0, 0), (0, 1)))
    n6 = np.pad(img[:-1, :], ((1, 0), (0, 0)))
    n2 = np.pad(img[1:, :], ((0, 1), (0, 0)))
    n1 = np.pad(n2[:, 1:], ((0, 0), (0, 1)))
    n7 = np.pad(n6[:, 1:], ((0, 0), (0, 1)))
    n3 = np.pad(n2[:, :-1], ((0, 0), (1, 0)))
    n5 = np.pad(n6[:, :-1], ((0, 0), (1, 0)))
    n0_logic = np.logical_not(n0) & (n4 & (n5 + n6 + n2 + n3) & (n6 + np.logical_not(n7)) & (n2 + np.logical_not(n1)))
    n4_logic = np.logical_not(n4) & (n0 & (n1 + n2 + n6 + n7) & (n2 + np.logical_not(n3)) & (n6 + np.logical_not(n5)))
    n2_logic = np.logical_not(n2) & (n6 & (n7 + n0 + n4 + n5) & (n0 + np.logical_not(n1)) & (n4 + np.logical_not(n3)))
    n6_logic = np.logical_not(n6) & (n2 & (n3 + n4 + n0 + n1) & (n4 + np.logical_not(n5)) & (n0 + np.logical_not(n7)))
    logical = n0_logic+n4_logic+n2_logic+n6_logic

    return np.uint8(np.where(logical,False,img))*255

spta_img = SPTA(canny_img)

fig = plt.figure(figsize=(13,13))
plt.subplot(121)
plt.imshow(canny_img,cmap='gray')
plt.xlabel('canny')

plt.subplot(122)
plt.imshow(spta_img,cmap='gray')
plt.xlabel('spta_img')
fig.tight_layout()
plt.show()

fig = plt.figure(figsize=(13,13))
plt.subplot(121)
plt.imshow(canny_img[250:350,200:300],cmap='gray')
plt.xlabel('canny')

plt.subplot(122)
plt.imshow(spta_img[250:350,200:300],cmap='gray')
plt.xlabel('spta_img')
fig.tight_layout()
plt.show()

에지 추적

에지 추적은 에지를 연결하는 방법에 대한 것이다.
먼저 끝점과 분기점을 수집하여 추적 시작점으로 사용한다. 끝점과 분기점을 판단할 때 전환 횟수를 체크한다. 전환 횟수는 다음과 같이 시계 방향으로 에지에서 비에지로 변하는 횟수이다.

이때 전환 횟수가 1인 지점은 끝점, 3이상인 지점은 분기점으로 판단한다.

다음으로, 에지 토막을 추적한다.
주변 픽셀 중 전환 횟수가 2인 점에서 전방화소 중 에지맵에서 1인 픽셀을 끝점에서 끝점 혹은 분기점 까지 찾아 segment에 추가하는 방식이다.
전방화소는 다음과 같다.

대각선일 때는 5개의 전방화소를 갖고 상하좌우일 때는 3개의 전방화소를 갖는다.

에지 추적 코드구현

import queue

def front_pixel(y,x,dir):
    if dir%2==0:
        front=np.zeros((3,3),np.int32)
    else:
        front = np.zeros((5,3),np.int32)

    if dir==0:
        front[0]=[y-1,x+1,7]
        front[1]=[y,x+1,0]
        front[2] =[y+1,x+1,1]
    elif dir==1:
        front[0]=[y-1,x+1,7]
        front[1] =[y,x+1,0]
        front[2] = [y + 1, x + 1, 1]
        front[3] = [y + 1, x, 2]
        front[4] = [y + 1, x-1, 3]
    elif dir==2:
        front[0] = [y + 1, x + 1, 1]
        front[1] = [y + 1, x, 2]
        front[2] = [y + 1, x-1, 3]
    elif dir==3:
        front[0] = [y + 1, x + 1, 1]
        front[1] = [y + 1, x, 2]
        front[2] = [y + 1, x - 1, 3]
        front[3] = [y,x-1,4]
        front[4] = [y-1,x-1,5]
    elif dir==4:
        front[0] = [y + 1, x - 1, 3]
        front[1] = [y, x - 1, 4]
        front[2] = [y - 1, x - 1, 5]
    elif dir==5:
        front[0] = [y + 1, x - 1, 3]
        front[1] = [y, x - 1, 4]
        front[2] = [y - 1, x - 1, 5]
        front[3] = [y-1,x,6]
        front[4] = [y-1,x+1,7]
    elif dir == 6:
        front[0] = [y - 1, x - 1, 5]
        front[1] = [y - 1, x, 6]
        front[2] = [y - 1, x + 1, 7]
    elif dir == 7:
        front[0] = [y - 1, x - 1, 5]
        front[1] = [y - 1, x, 6]
        front[2] = [y - 1, x + 1, 7]
        front[3]=[y,x+1,0]
        front[4] =[y+1,x+1,1]
    return front
def edge_segment_detection(spta_img):
    dx = np.array([0, 1, 1, 1, 0, -1, -1, -1])
    dy = np.array([1, 1, 0, -1, -1, -1, 0, 1])
    spta_img[0, :] = spta_img[:, 0] = spta_img[-1, :] = spta_img[:, -1] = 0
    spta_img //=255
    c = np.zeros_like(spta_img)
    
    ## 전환횟수 체크
    for i in range(1, len(spta_img) - 1):
        for j in range(1, len(spta_img[0]) - 1):
            flag = False
            count = 0
            for z in range(9):
                idx = z
                if z == 8:
                    idx = 0
                y = i + dy[idx]
                x = j + dx[idx]
                if flag and spta_img[y, x] == 0:
                    count += 1
                    flag=False
                elif spta_img[y, x]:
                    flag = True
                else:
                    flag=False

            c[i, j] = count

    zeros = np.zeros([spta_img.shape[0], spta_img.shape[1], 3], np.uint8)

    # start and branch, red = start or end point, blue = branch
    zeros[..., 0] = spta_img * 255
    zeros[..., 1] = spta_img * 255
    zeros[..., 2] = spta_img * 255

    zeros[..., 1] = np.where(c == 1, 0, zeros[..., 1])
    zeros[..., 2] = np.where(c == 1, 0, zeros[..., 2])
    zeros[..., 0] = np.where(c >= 3, 0, zeros[..., 0])
    zeros[..., 1] = np.where(c >= 3, 0, zeros[..., 1])
    plt.imshow(zeros)
    plt.xlabel("start")
    plt.show()

    # 끝점 혹은 분기점 입력
    Q = queue.Queue()
    for i in range(1, len(spta_img) - 1):
        for j in range(1, len(spta_img[0]) - 1):
            if c[i, j] == 1 or c[i, j] >= 3:
                for z in range(8):
                    y = dy[z] + i
                    x = dx[z] + j
                    if spta_img[y, x]:
                        Q.put((i, j, z))

    n = 0
    visited = np.zeros_like(spta_img)
    segment = []
    while not Q.empty():
        (y, x, dir) = Q.get()
        cy = dy[dir] + y
        cx = dx[dir] + x

        if visited[cy, cx]:
            continue
        n += 1
        n_seg = []
        n_seg.append([y, x])
        n_seg.append([cy, cx])
        visited[y, x] = visited[cy, cx] = 1

        if c[cy, cx] == 1 or c[cy, cx] >= 3:
            continue
        while True:
            flag = False
            fronts = front_pixel(cy, cx, dir)
            for f in fronts:
                n_y, n_x, n_dir = f
                if c[n_y, n_x] == 1 or c[n_y, n_x] >= 3:
                    flag = True
                    visited[n_y, n_x] = 1
                    n_seg.append([n_y, n_x])
                    break
            if flag:
                break
            else:
                for f in fronts:
                    n_y, n_x, n_dir = f
                    if spta_img[n_y, n_x]:
                        n_seg.append([n_y, n_x])
                        visited[n_y, n_x] = 1
                        dir = n_dir
                        cy = n_y
                        cx = n_x
                        flag = True
                        break
                if flag == False:
                    break

        segment.append(n_seg)
    return segment
plt.figure(figsize=(13,13))
spta_img = SPTA(canny_img)

small = spta_img
segmented = edge_segment_detection(small)


for s in segmented:
    if len(s)>5:
        edge = np.zeros([small.shape[0],small.shape[1]],np.uint8)
        fig = plt.figure(figsize=(13,13))
        for i in s:
            y,x = i
            edge[y,x]=255
        plt.imshow(edge,cmap='gray')
        plt.show()

아래 그림에서 빨간점은 끝점, 파란점은 분기점이다.

다음은 segment된 집합 중 하나이다.

fig = plt.figure(figsize=(13,13))
edge2 = np.zeros([small.shape[0],small.shape[1]],np.uint8)
for s in segmented:
    for i in s:
        y,x = i
        edge2[y,x]=255

plt.imshow(edge2,cmap='gray')
plt.show()