Python/파이썬 OpenCV 공부

[OpenCV 머신러닝] OpenCV로 k 최근접 이웃 알고리즘(KNN) 구현하기 - cv2.ml.KNearest_create

AI 꿈나무 2020. 10. 24. 20:50
반응형

k 최근접 이웃 알고리즘 - KNN, k-Nearest Neighbor

 k 최근접 이웃 알고리즘은 특징 공간에서 테스트 데이터와 가장 가까이 있는 k개의 학습 데이터를 찾아 분류 또는 회귀를 수행하는 지도 학습 알고리즘의 하나입니다.

 

 

 학습 데이터가 녹색이라고 가정하였을 때 원래 데이터(파랑색, 빨강색)중 가장 가까지 있는 데이터의 클래스를 부여합니다.

 위 사진에서 녹색 데이터는 빨강색 데이터와 가까이 있지만 빨강색 데이터라고 판단하기에는 애매합니다.

 이처럼 잘못 분류될 가능성이 있는데 이를 해결하기 위해 가장 가까이 있는 것을 k개 찾습니다.

 선별한 k개 데이터에서 파랑색, 빨강색 데이터 개수를 세어 더 많이 선별된 데이터의 클래스를 부여하게 됩니다.

 예를 들어 5개 데이터를 선별했을 때 3개가 파랑색, 2개가 빨강색이면 파랑색 클래스를 부여합니다.

 

 

OpenCV에서 KNN 알고리즘 구현하기

 OpenCV에서 KNN 알고리즘 클래스를 제공하고 있습니다.

 KNN 객체 생성 -> 학습 -> 추론의 순서로 진행됩니다. 

1. KNN 알고리즘 객체 생성 - cv2.ml.KNearest_create

cv2.ml.KNearest_create() -> retval

• retval: cv2.ml_KNearest 객체

 객체를 생성하면 k=10 디폴트 값으로 설정되어 있습니다.

 k 값이 너무 많아도 일반화 되어 변별력이 떨어지게 됩니다.

 k는 적당한 값을 지정해줘야 하는데 .findNearest 함수로 k 값을 지정할 수 있습니다.

 

2. KNN 알고리즘 학습 - cv2.ml_KNearest.train

cv2.ml_KNearest.train(samples, layout, responses) -> retval

• samples: 학습 데이터 행렬. numpy.ndarray. shape=(N, d), dtype=numpy.float32.

• layout: 학습 데이터 배치 방법.
  cv2.ROW_SAMPLE : 하나의 데이터가 한 행으로 구성됨
  cv2.COL_SAMPLE : 하나의 데이터가 한 열로 구성됨

• responses: 각 학습 데이터에 대응되는 응답(레이블) 행렬. numpy.ndarray. shape=(N, 1), dtype=numpy.int32 또는 numpy.float32.

• retval: 학습이 성공하면 True.

 layout은 보통 cv2.ROW_SAMPLE을 입력합니다.

 

3. KNN 알고리즘으로 입력 데이터의 클래스 예측 - cv2.ml_KNearest.findNearest

 .findNearest는 .predict와 같은 기능입니다.

 finNearest 함수에는 k 인자가 있어 k 값을 자유롭게 변경할 수 있는 장점이 있습니다.

 또한 결과값이 predict 보다 2개 더 있어 추가적인 정보를 얻을 수 있습니다.

 

cv.ml_KNearest.findNearest(samples, k, results=None, neighborResponses=None, dist=None , flags=None) -> retval, results, neighborResponses, dist

• samples: 입력 벡터가 행 단위로 저장된 입력 샘플 행렬. numpy.ndarray. shape=(N, d), dtype=numpy.float32.

• k: 사용할 최근접 이웃 개수

• results: 각 입력 샘플에 대한 예측(분류 또는 회귀) 결과를 저장한 행렬. numpy.ndarray. shape=(N, 1), dtype=numpy.float32.

• neighborResponses: 예측에 사용된 k개의 최근접 이웃 클래스 정보 행렬. numpy.ndarray. shape=(N, k), dtype=numpy.float32.

• dist: 입력 벡터와 예측에 사용된 k개의 최근접 이웃과의 거리를 저장한 행렬. numpy.ndarray. shape=(N, k), dtype=numpy.float32.

• retval: 입력 벡터가 하나인 경우에 대한 응답

 .findNearest 함수는 neighborResponses와 dist 값을 추가로 받을 수 있습니다.

 neighborResponses는 근접 데이터 정보를 갖고 있습니다.

 dist는 L2 norm 정보를 제공합니다.

 

KNN 알고리즘 점 분류 예제코드

 예제 코드 출처 :  황선규 박사님 github홈페이지 sunkyoo.github.io/opencv4cvml/

 트랙바를 이용해서 k값에 따른 결과값을 비교할 수 있는 코드입니다.

 예제 코드를 보고 분석해보았습니다.

 

# 트랙바 콜백 함수
def on_k_changed(pos):
    global k_value
    
    k_value = pos
    if k_value < 1:
        k_value = 1
        
    trainAndDisplay()
    
# 리스트로 변환하여 train과 label에 데이터 저장
def addPoint(x, y, c):
    train.append([x, y])
    label.append([c])

# 시각화 함수
def trainAndDisplay():
    # train, label 90개의 데이터를 ndarray로 저장합니다.
    # train은 float32, label은 int32로 입력해야 합니다.
    trainData = np.array(train, dtype=np.float32)
    labelData = np.array(label, dtype=np.int32)
    
    # ROW_SAMPLE 인자는 데이터 하나가 한 행으로 들어가는 것을 의미합니다.
    knn.train(trainData, cv2.ml.ROW_SAMPLE, labelData)
    
    # 영상의 모든 픽셀을 클래스에 맞게 색칠
    h, w = img.shape[:2]
    for y in range(h):
        for x in range(w):
            sample = np.array([[x, y]]).astype(np.float32) # 영상의 모든 픽셀
            
            # 모든 픽셀에 대해 몇번 클래스인지 계산합니다.
            # ret이 아닌 res를 받아오고 rest[0,0]도 가능합니다.
            ret, _, _, _ = knn.findNearest(sample, k_value)
            
            ret = int(ret)
            if ret == 0:
                img[y, x] = (128, 128, 255) # 빨강
            elif ret == 1:
                img[y, x] = (128, 255, 128) # 녹색
            elif ret == 2:
                img[y, x] = (255, 128, 128) # 파랑
    
    # train 데이터를 원으로 시각화
    for i in range(len(train)):
        x, y = train[i]
        l = label[i][0]
        
        if l == 0:
            cv2.circle(img, (x, y), 5, (0, 0, 128), -1, cv2.LINE_AA)
        elif l == 1:
            cv2.circle(img, (x, y), 5, (0, 128, 0), -1, cv2.LINE_AA)
        elif l == 2:
            cv2.circle(img, (x, y), 5, (128, 0, 0), -1, cv2.LINE_AA)
        
    cv2.imshow('knn',img)
            
# 학습 데이터 & 레이블
# 2차원 평면 상에 점들을 찍고 3개의 점들로 구분.
train = []
label = []

k_value = 1 # 초기값
img = np.full((500, 500, 3), 255, np.unit8) # 컬러 영상 제작
knn = cv2.ml.KNearest_create() # KNearest 객체 생성

# 랜덤 데이터 생성
NUM = 30
rn = np.zeros((NUM, 2), np.int32) # 30행 2열, 60개

# (150, 150) 근방의 점은 0번 클래스로 설정
cv2.randn(rn, 0, 50) # 가우시안 분포를 따르는 함수
for i in range(NUM):
    addPoint(rn[i, 0] + 150, rn[i,1] + 150, 0) # x,y 값에 150을 더함

# (350, 150) 근방의 점은 1번 클래스로 설정
cv2.randn(rn, 0, 50)
for i in range(NUM):
    addPoint(rn[i, 0] + 350, rn[i, 1] + 150, 1)

# (250, 400) 근방의 점은 2번 클래스로 설정
cv2.randn(rn, 0, 70)
for i in range(NUM):
    addPoint(rn[i, 0] + 250, rn[i, 1] + 400, 2)

# 영상 출력 창 생성 & 트랙바 생성
cv2.namedWindow('knn')
cv2.createTrackbar('k_value', 'knn', 1, 5, on_k_changed)

# KNN 결과 출력
trainAndDisplay()

cv2.waitKey()
cv2.destroyAllWindows()

 

k=1 일때

 

k=2 일때  

 

k=4 일때

 

 k가 증가함에 따라 데이터 영역이 변화되는 것을 확인할 수 있습니다.

 


OpenCV 홈페이지와 황선규 박사님의 'OpenCV 4로 배우는 컴퓨터 비전과 머신 러닝' 을 공부하면서 정리해 보았습니다.

 

OpenCV: K-Nearest Neighbour

OpenCV  4.3.0 Open Source Computer Vision

docs.opencv.org

반응형