반응형




해리스 코너 디텍터를 사용하여 검출한 코너점을 사용하여 두 장의 이미지를 매칭하는 예제입니다.

코너점의 방향을 기준으로 이미지 패치를 회전시키서 매칭점인지 비교하기 때문에 다른 부분의 코너점인데 매칭될 수 있습니다.

 

2019. 5. 29 최초작성

2020. 9. 14 최근 OpenCV 버전에서 문제 되는 코드 수정




두 장의 이미지에서 각각 코너점을 찾은 후, 유사한 코너점끼리 매칭을 해준 결과입니다.

 





다음 단계를 거쳐 코너점을 매칭합니다.



1. 해리스 코너 디텍터로 코너점을 찾습니다.

 

 

2. 코너점을 기준으로 일정 크기의 이미지를 ROI 합니다.

 




..


..

3.  코너점을 중심점으로 하는 일정크기의 패치를 얻었습니다.

일치하는 점인것 확실 하지만 이미지 방향이 다른 상태입니다.

 





4. 코너점의 방향을 구하여 이미지를 회전시켜보면 유사한 이미지가 됩니다.





5. 회전된 이미지에서 이미지 중심점을 기준으로 일정 범위를 ROI한 후,  5 x 5 크기의 이미지로 축소합니다.

그리고 1차원 배열로 바꾸어줍니다.




6. 첫번째 이미지 패치를 기준으로 두번째 이미지 패치에서 유사한 것을 찾아 기록합니다.

NCC(Normalized Cross Correlation)를 사용합니다.


7. 두번쨰 이미지 패치를 기준으로 첫번째 이미지 패치에서 유사한 것을 찾아 기록합니다.

NCC(Normalized Cross Correlation)를 사용합니다.


8. 6번과 7번 매칭 결과 중 공통되는 것만 가지고 매칭 결과 이미지를 만듭니다.



 

 

전체 소스코드입니다.

import cv2
import numpy as np



# 해리스 코너를 사용하여 코너점을 찾습니다.
def find_points(image,block_size=5, k=0.05):
    
    gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)

    gray = np.float32(gray)
    dst = cv2.cornerHarris(gray,block_size,3,k)
    dst = cv2.dilate(dst,None)
    ret, dst = cv2.threshold(dst,0.01*dst.max(),255,0)
    dst = np.uint8(dst)
    ret, labels, stats, centroids = cv2.connectedComponentsWithStats(dst)
    criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 100, 0.001)
    corners = cv2.cornerSubPix(gray,np.float32(centroids),(5,5),(-1,-1),criteria)


    points=[]
    for i in range(1, len(corners)):
        points.append([int(corners[i,0]),int(corners[i,1])])
    
    points = np.asarray(points)
   
    return points


# 코너점의 방향을 계산합니다.
def ori_points(image, points):
    
    orientation = np.zeros((points.shape[0], 1))
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    gray = np.float32(gray)
    sobel = np.array([[-1.0, 0.0, 1.0],[-2.0, 0.0, 2.0],[-1.0, 0.0, 1.0]])
    
    ori_x = cv2.filter2D(gray, -1, sobel)
    ori_y = cv2.filter2D(gray, -1, sobel.T)


    for i in range(points.shape[0]):
        x = points[i, 0]
        y = points[i, 1]

        angle = np.arctan2(-ori_y[y, x], ori_x[y, x]) * 180/np.pi

        if angle < 0.0:
            orientation[i,:]=(angle+360.0)
        else:
            orientation[i,:]=(angle)
        
    return orientation
     


# 이미지 패치를 회전합니다. 
def rotation(image, angle):

    (h, w) = image.shape
    (cX, cY) = (w // 2, h // 2)


    rotation_matrix = cv2.getRotationMatrix2D((cX, cY), int(-angle), 1.0)
    
    cos = np.abs(rotation_matrix[0, 0])
    sin = np.abs(rotation_matrix[0, 1])
    
    newWidth = int((h * sin) + (w * cos))
    newHeight = int((h * cos) + (w * sin))
 
    rotation_matrix[0, 2] += (newWidth / 2) - cX
    rotation_matrix[1, 2] += (newHeight / 2) - cY
 
    dst=cv2.warpAffine(image, rotation_matrix, (newWidth, newHeight))
    
    return dst
    

# 코너점을 중심으로한 descriptor를 구합니다. 
def points_descriptor(image,points,orientation,num):

    descriptor=np.zeros((points.shape[0],25))
    gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
    gray = np.float32(gray)

    gray=cv2.copyMakeBorder(gray,100,100,100,100, borderType=cv2.BORDER_REPLICATE)

    for i in range(points.shape[0]):
        x = points[i, 0] + 100
        y = points[i, 1] + 100
 
        patch = gray[y-40:y+40, x-40:x+40]
        new_patch = rotation(patch, orientation[i,:])

        cY,cX = int(new_patch.shape[0]/2), int(new_patch.shape[1]/2)
        new_patch = new_patch[cY-20:cY+20,cX-20:cX+20]

        new_patch = cv2.resize(new_patch,(5,5),interpolation=cv2.INTER_AREA)


        a=new_patch.flatten()

        if a.all() == False:
            descriptor[i,:] = np.zeros(25)
        else:
            descriptor[i,:]= a
        
    return descriptor


def feature_match(des1, des2):
    
    match1=[]
    match2=[]

    
    for i in range(des1.shape[0]):
        temp = []
        for j in range(des2.shape[0]):
        
            if des1[i,:].all() != False and des2[j,:].all() != False:

                p1 = des1[i,:]
                p2 = des2[j,:]

                d = np.sum((p1 - np.mean(p1))*(p2 - np.mean(p2))/(np.std(p1)*np.std(p2)))
                d = d / 25
                temp.append(d)
            else:
                temp.append(-1)

        d = np.argmax(temp) 
        if temp[d] > 0.5:
            match1.append((i, d))


    for j in range(des2.shape[0]):
        temp = []
        for i in range(des1.shape[0]):
    
            if des1[i,:].all() != False and des2[j,:].all() != False:

                p1 = des1[i,:]
                p2 = des2[j,:]

                d = np.sum((p1 - np.mean(p1))*(p2 - np.mean(p2))/(np.std(p1)*np.std(p2)))
                d = d / 25
                temp.append(d)
            else:
                temp.append(-1)

        d = np.argmax(temp) 
        if temp[d] > 0.5:
            match2.append((d, j))

    match = list(set(match1).intersection(set(match2)))

    return match



f1 = cv2.imread("1.png")
f2 = cv2.imread("2.png")

# 코너점을 찾습니다.
p1= find_points(f1,block_size=5, k=0.04)
p2= find_points(f2,block_size=5, k=0.04)

# 코너점 방향을 구합니다.
ori1 = ori_points(f1, p1)
ori2 = ori_points(f2, p2)


# 코너점을 이미지로 저장합니다. 
a1 = f1.copy()
a2 = f2.copy()

for i in range(p1.shape[0]):
    cv2.circle(a1, (p1[i, 0], p1[i, 1]), 3, (0,255,0), -1)

cv2.imwrite("point1.jpg", a1)

for i in range(p2.shape[0]):
    cv2.circle(a2, (p2[i, 0], p2[i, 1]), 3, (0,255,0), -1)

cv2.imwrite("point2.jpg", a2)


# descriptor를 구합니다. 
des1 = points_descriptor(f1,p1,ori1,1)
des2 = points_descriptor(f2,p2,ori2,2)


# 매칭을 구합니다. 
match_result= feature_match(des1, des2)

print(len(match_result))



# 매칭을 그리기 위한 작업입니다. 
kp1 = [cv2.KeyPoint(int(p1[i,0]), int(p1[i,1]), 5) for i in range(p1.shape[0])]
kp2 = [cv2.KeyPoint(int(p2[i,0]), int(p2[i,1]), 5) for i in range(p2.shape[0])]
matches = [cv2.DMatch(i[0], i[1], 1) for i in match_result]

draw_params = dict(matchColor = (0, 255, 0),       # 매칭된 점은 초록색
                   singlePointColor = (255, 0, 0), # 매칭 안된 점은 파란색
                   flags = 0)

matching = cv2.drawMatches(f1, kp1, f2, kp2, matches, None,**draw_params)


cv2.imshow("result", matching)
cv2.waitKey(0)


참고

[1] https://github.com/ImXman/Computer_Visison_Feature_Matching/blob/b6b7f245e3b1f30b2f8bca5e1aff5c2ddb435fdf/feature_matching.py

 

[2] https://stackoverflow.com/questions/50984205/how-to-find-corners-points-of-a-shape-in-an-image-in-opencv

 

[3] https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_feature2d/py_features_harris/py_features_harris.html

 

[4] https://yeslab.tistory.com/26

반응형

포스트 작성시에는 문제 없었지만 이후 문제가 생길 수 있습니다.
댓글로 알려주시면 빠른 시일내에 답변을 드리겠습니다.

여러분의 응원으로 좋은 컨텐츠가 만들어집니다.
지금 본 내용이 도움이 되었다면 유튜브 구독 부탁드립니다. 감사합니다 : )

유튜브 구독하기


제가 쓴 책도 한번 검토해보세요.

  1. 김찬영 2020.05.14 17:02

    영상과 사진을 매칭하는 것도 가능할까요?

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2020.05.14 23:15 신고

      OpenCV에서 영상을 연속적인 이미지로 다루기 때문에 가능합니다.

      영상이 재생됨에 따라 계속 바뀌는 이미지와 고정된 이미지를 반복적으로 계속 매칭한다고 보면 됩니다.

  2. Favicon of https://beakgong.tistory.com BlogIcon 백공잉 2020.09.12 13:16 신고

    잘보고 있습니다.
    한가지 질문좀 드리겠습니다. VSCODE로 파이썬 코딩중 아래와 같은 오류가 발생했습니다.
    Traceback (most recent call last):
    File "d:\opencv_example\templet_match1.py", line 167, in <module>
    p1= find_points(f1,block_size=5, k=0.04)
    File "d:\opencv_example\templet_match1.py", line 9, in find_points
    gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
    cv2.error: OpenCV(4.4.0) C:\Users\appveyor\AppData\Local\Temp\1\pip-req-build-q0nmoxxv\opencv\modules\imgproc\src\color.cpp:182: error: (-215:Assertion failed) !_src.empty() in function 'cv::cvtColor'

    opencv 버전에 관련된거 같은데요. 무슨 에러 일까요?

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2020.09.12 14:34 신고

      cvtColor 함수의 입력인 image 변수에 이미지가 없다는 에러입니다. image 변수에 이미지 로드하는 부분을 확인해보새요

    • Favicon of https://beakgong.tistory.com BlogIcon 백공잉 2020.09.14 20:09 신고

      감사합니다. 해결되었습니다.
      간단한거였네요.. ㅜㅜ;

      이미지를 회전하는 방식이 화면에 출력이 되던거 같은데. 위 코드에서는 출력을 따로 주는거 같진 않은데요. 설명처럼 이미지를 출력하면서 진행과정을 알려주면 도움이 될거 같습니다.

      중간 중간 cv2.imshow 구문을 주면 좋겠지만 저로써는 불가능에 가깝네요 ㅜㅜ;

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2020.09.14 20:44 신고

      다행입니다

  3. Favicon of https://beakgong.tistory.com BlogIcon 백공잉 2020.09.14 20:16 신고

    죄송합니다. 한가지만 더 물어보겠습니다.

    Traceback (most recent call last):
    File "d:\opencv_example\templet_match1.py", line 191, in <module>
    des1 = points_descriptor(f1,p1,ori1,1)
    File "d:\opencv_example\templet_match1.py", line 94, in points_descriptor
    new_patch = rotation(patch, orientation[i,:])
    File "d:\opencv_example\templet_match1.py", line 64, in rotation
    rotation_matrix = cv2.getRotationMatrix2D((cX, cY), -angle, 1.0)
    TypeError: Argument 'angle' can not be treated as a double

    이 에러에 대해서 설명 부탁 드립니다.

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2020.09.14 20:55 신고

      에러나는 부분들을 수정해놓았습니다. 테스트해보세요

+ Recent posts