Hasznos feladat 2D sík mintázatok térbeli vetítéseinek torzulását korrigálni automatikus pontpárosító módszerrel. Az alap algoritmust az ocv_pair_homography_crosscheck.py példaprogram mutatja be.
A szükséges importálások.
#
# https://docs.opencv.org/4.0.1/db/d70/tutorial_akaze_matching.html
#
import numpy as np
import cv2
from matplotlib import pyplot as plt
Az algoritmus működését globális változókkal szabályozzuk. A METHOD 'AKAZE' vagy 'ORB' lehet. A MIN_MATCH_COUNT azt mondja meg, hogy a RANSAC hívás előtt minimálisan hány pontpárra van szükség. Ennél kevesebb esetén feladjuk a párosítást. A CROSS_CHECK a kereszt-validáció, a RADIUS_MATCH a távolság alapú szűrés kapcsolója, bool típusú értékkel.
METHOD: str = 'AKAZE'
MIN_MATCH_COUNT: int = 10
EDGE_OVERLAY: bool = False
CROSS_CHECK: bool = True
RADIUS_MATCH: bool = True
Ha van, a rádiusz szűrés értéke 50.
radius_thresh: int = 50
Be- és kimeneti fájlnevek definiálása, képek beolvasása
img1Name = 'clock2_0367_rs.jpg'
img2Name = 'clock2_0363_rs.jpg'
resultName = 'clock2_0367_0363_reg.jpg'
out_base_name = 'clocks_1nn_{0}'.format(METHOD)
if CROSS_CHECK:
out_base_name = out_base_name + '_crosscheck'
if RADIUS_MATCH:
out_base_name = out_base_name + '_radius{:03d}'.format(radius_thresh)
img1 = cv2.imread(img1Name, 0) # baseImage
img2 = cv2.imread(img2Name, 0) # queryImage
img2_gr_bgr = cv2.merge((img2, img2, img2))
Kulcspont detektor kinyerő objektum létrehozása, kulcspont és leíró számítása mindkét képre egymástól függetlenül.
# Initiate detector
if METHOD == 'AKAZE':
kpextract = cv2.AKAZE_create()
else:
kpextract = cv2.ORB_create()
# Find the keypoints and descriptors
kp1, des1 = kpextract.detectAndCompute(img1, None)
kp2, des2 = kpextract.detectAndCompute(img2, None)
print('Keypoints in img1:', len(kp1))
print('Keypoints in img2:', len(kp2))
img1kp1 = cv2.drawKeypoints(img1, kp1, None, (255, 0, 0), 4)
img2kp2 = cv2.drawKeypoints(img2, kp2, None, (255, 0, 0), 4)
cv2.imshow('img1kp1', img1kp1)
cv2.imshow('img2kp2', img2kp2)
cv2.imwrite('{0}_img1kp1.png'.format(out_base_name), img1kp1)
cv2.imwrite('{0}_img2kp2.png'.format(out_base_name), img2kp2)
cv2.waitKey(0)
Leíró hasonlóságon alapuló párosítás. A cv2.BFMatcher() függvény egy összes esetet kiértékelő (brute force) objektumot hoz létre. Meg kell adni a leírók összehasonlításának módszerét. Ez Euklidészi vagy Hamming-jellegű távolság lehet, attól függően, hogy a leíró vektorok milyen jellegű információt hordoznak. Klasszikus, számértékeket tartalmazó leírók például a SIFT és a SURF. Az AKAZE és ORB detektorok bináris leíróvektorokat használnak, ebben az esetben az eltérő bitpozíciók száma adja a távolságot. Második paraméterként megadhatjuk, végezzen-e a párosító kereszt-validációt.
# Matching based on descriptor similarity
# HAMMING should be used for binary descriptors (ORB, AKAZE, ...)
# NORM_L2 (Euclidean distance) can be used for non-binary descriptors (SIFT, SURF, ...)
matcher = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=CROSS_CHECK)
Ha rádiusz szűrés kezelését megvalósíthatjuk az alábbi módon. A good_match lista fogja a szűrt eredményt tárolni. A példában megmutatjuk azt is, hogyan lehet a Match objektum adataihoz hozzáférni. A queryIdx és trainIdx a két kulcspontlista indexét adja a párra, a distance a két leíró távolságát.
if RADIUS_MATCH:
radius_match = matcher.radiusMatch(des1, des2, radius_thresh)
good_match = []
for m in radius_match:
if m:
print(m[0].queryIdx, m[0].trainIdx, m[0].distance)
good_match.append(m[0])
else:
good_match = matcher.match(des1, des2)
Fontos megérteni, hogy ezek előzetes, potenciális párok csak! A kulcspontok egyméshoz képesti geometriai elhelyezkedése nem került még figyelembe vételre! Megjelenítjük a potenciális párokat. A cv2.drawMatches() végzi a vizualizációt. A működését számos paraméterrel szabályozhatjuk, amit célszerű egy Python dict objektumba kigyűjteni. A függvény egymás mellett jeleníti meg a két képet, és összeköti a potenciális párokat. Megadhatjuk, hogy a párosítatlan pontok kirajzolásra kerüljenek-e (SINGLE_POINT)?
# Visualize matches
print("Potential good matches found:", len(good_match))
draw_params = dict(matchColor=(255, 0, 0), # draw matches in blue color
singlePointColor=(0, 0, 255), # draw non-matched points in red
matchesMask=None, # draw everything
flags=cv2.DrawMatchesFlags_DEFAULT)
img5 = cv2.drawMatches(img1, kp1, img2, kp2, good_match, None, **draw_params)
cv2.imshow('Crosscheck: Potential good matches', img5)
cv2.imwrite('{0}_pgm.png'.format(out_base_name), img5)
cv2.waitKey(0)
Amennyiben van elegendő számú potenciális pontpárunk, RANSAC segítségével kiszűrjük a geometriailag nem korrekt párokat. A pár listából az OpenCV-nek megfelelő src_pts és dst_pts struktúrákat állítjuk elő.
# Filter outlier matches using RANSAC assuming homography between objects
if len(good_match) > MIN_MATCH_COUNT:
src_pts = np.float32([kp1[m.queryIdx].pt for m in good_match]).reshape(-1, 1, 2)
dst_pts = np.float32([kp2[m.trainIdx].pt for m in good_match]).reshape(-1, 1, 2)
A pontpár halmazokra végrehajtuk a RANSAC algoritmust, közöttük síkhomográfiát feltételezve. Fontos megérteni, hogy ez a modell arra alkalmas, hogy térben elhelyezett sík területek közötti viszonyt írja le, tetszőleges térbeli színtér kulcspontjaira nem fog jól működni! Ha ilyen jellegű geometriai szűrést szeretnénk végezni, akkor az epipoláris geometriai kényszert kell alkalmaznuk. Ezzel az utolsó témakörben fogunk foglalkozni. Eredményként megkapjuk a a szűrt ponthalmazok között ható síkhomográfia transzformációt, és egy maszk tömböt, amely 0 és 1 értékekkel jelzi, hogy az adott pontpár-index megtartásra vagy kiszűrésre került.
# Find homography with outlier filtering
M, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 5.0)
print('Matching keypoints after RANSAC:', np.count_nonzero(mask))
outliersMask = 1 - mask
matchesMask = mask.ravel().tolist()
matchesOutliersMask = outliersMask.ravel().tolist()
A síkhomográfiát végrehajtuk az img1 képre és a négy sarokpontjára. Ez utóbbi segítségével tudjuk az img1 kép transzformált körvonalát az img2 képbe rajzolni majd.
# Perspective transformation of img1 and its corner points
h, w = img1.shape[:2]
h2, w2 = img2.shape[:2]
img1_persp = cv2.warpPerspective(img1, M, (w2, h2))
cv2.imwrite(resultName, img1_persp)
# 4 corner point coordinates of img1
pts = np.float32([[0, 0], [0, h - 1], [w - 1, h - 1], [w - 1, 0]]).reshape(-1, 1, 2)
# Transformed to img2
pts_persp = cv2.perspectiveTransform(pts, M)
Előállítjuk az illesztett képek vörös-zöld fúzióját.
# Blend registered images
if EDGE_OVERLAY:
img1_persp = cv2.Canny(img1_persp, 100, 300)
image_blend = cv2.merge((np.zeros((h2, w2), np.uint8), img2, img1_persp))
Berajzoljuk a transzformált img1 határvonalat sárga színnel az img2 képbe.
# Draw bounding rectangle of img1 over img2
img2 = cv2.polylines(img2_gr_bgr, [np.int32(pts_persp)], True, (0, 255, 255), 3, cv2.LINE_AA)
Inlier és outlier párosítások, és a fúzionált eredmény megjelenítése.
# Draw keypoint matches
draw_params = dict(matchColor=(0, 255, 255), # draw outlier matches in yellow color
singlePointColor=(0, 0, 255),
matchesMask=matchesOutliersMask, # draw only outliers
flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)
img3 = cv2.drawMatches(img1, kp1, img2, kp2, good_match, None, **draw_params)
cv2.imshow('Outlier matches', img3)
cv2.imwrite('{0}_outm.png'.format(out_base_name), img3)
cv2.waitKey(0)
draw_params = dict(matchColor=(0, 255, 0), # draw matches in green color
singlePointColor=(0, 0, 255),
matchesMask=matchesMask, # draw only inliers
flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)
img4 = cv2.drawMatches(img1, kp1, img2, kp2, good_match, None, **draw_params)
cv2.imshow('Inlier matches', img4)
cv2.imwrite('{0}_inm.png'.format(out_base_name), img4)
cv2.waitKey(0)
cv2.imshow('Blended result', image_blend)
cv2.imwrite('{0}_blend.png'.format(out_base_name), image_blend)
Ha nincs elegendő pont a RANSAC-hoz, akkor üzenetet írunk a konzolra.
else:
print("Not enough matches are found - %d/%d" % (len(good_match), MIN_MATCH_COUNT))
matchesMask = None
Program lezárása.
cv2.waitKey(0)
cv2.destroyAllWindows()