서론
오늘은 OpenCV로 아루코마커 (ArUco marker)를 인식하는 것을 해보려고 한다. 아루코마커란 로봇 비전 혹은 컴퓨터 비전에서 많이 사용하는 마커이다. 마치 QR 코드처럼 우리가 카메라로 아루코마커를 인식하면 그 아루코마커가 가지고 있는 ID를 반환받아서 읽을 수 있다.
우리가 아루코마커를 사용하는 이유는 간편하기 때문이다. 간단히 아루코마커를 출력해서 붙여두면, 특정 위치에서 아루코마커를 인식할 수 있다. 물론, 우리가 객체인식 모델을 가지고 있다면, 굳이 아루코마커를 사용하지 않더라도 원하는 것을 인식하고 활용할 수 있다. 하지만 그렇게 딥러닝 모델을 만드는 것보다는 간단히 아루코마커를 출력해서 사용하는 편이 좋다.
아루코마커 (ArUco marker)
아루코마커를 간단히 만들 수 있는 사이트가 있다. 원래라면 OpenCV에 있는 라이브러리를 활용해서 아루코마커를 직접 만들 수 있다. 하지만 아래 첨부한 링크에서 간단하게 아루코마커를 만들 수 있으며, 나는 ID가 0, 1, 2, 3인 4x4 아루코마커를 네 개 만들어서 출력했다.
아루코마커 인식
위의 화면에서 볼 수 있듯이, 간단하게 아루코마커를 인식할 수 있는 코드를 만들어봤다. 여기에는 아직 카메라 캘리브레이션이 적용되지 않은 상태라서 왜곡이 있다. 하지만 아래에 나올 화면부터는 카메라 캘리블레이션이 적용되어 왜곡 없는 화면을 볼 수 있다. ([OpenCV] 1. 카메라 캘리브레이션 (Camera Calibration))
위의 사진처럼 아루코마커를 인식하면 특정 ID를 반환받을 수 있다. 즉, 우리는 이것을 활용해서 카메라가 어디를 보고 있고, 아루코마커를 기준으로 얼마나 떨어져있는지 등을 알 수 있다.
아루코마커를 통한 위치 추정
앞서 말했듯이 우리는 아루코마커를 통해 위치와 각도를 추정할 수 있다. 인식한 아루코마커의 위치와 각도에 따라서 x, y, z축 방향으로의 위치와 회전 각도를 계산할 수 있다.
import cv2
import numpy as np
import os
import time
import pickle
def live_aruco_detection(calibration_data):
"""
실시간으로 비디오를 받아 ArUco 마커를 검출하고 3D 포즈를 추정하는 함수
Args:
calibration_data: 카메라 캘리브레이션 데이터를 포함한 딕셔너리
- camera_matrix: 카메라 내부 파라미터 행렬
- dist_coeffs: 왜곡 계수
"""
# 캘리브레이션 데이터 추출
camera_matrix = calibration_data['camera_matrix']
dist_coeffs = calibration_data['dist_coeffs']
# ArUco 검출기 설정
aruco_dict = cv2.aruco.getPredefinedDictionary(cv2.aruco.DICT_4X4_250)
aruco_params = cv2.aruco.DetectorParameters()
detector = cv2.aruco.ArucoDetector(aruco_dict, aruco_params)
# 마커 크기 설정 (미터 단위)
marker_size = 0.05 # 예: 5cm = 0.05m
# 카메라 설정
cap = cv2.VideoCapture(0)
# 카메라 초기화 대기
time.sleep(2)
while True:
ret, frame = cap.read()
if not ret:
print("Failed to grab frame")
break
# 이미지 왜곡 보정
frame_undistorted = cv2.undistort(frame, camera_matrix, dist_coeffs)
# 마커 검출
corners, ids, rejected = detector.detectMarkers(frame_undistorted)
# 마커가 검출되면 표시 및 포즈 추정
if ids is not None:
# 검출된 마커 표시
cv2.aruco.drawDetectedMarkers(frame_undistorted, corners, ids)
# 각 마커의 포즈 추정
rvecs, tvecs, _ = cv2.aruco.estimatePoseSingleMarkers(
corners, marker_size, camera_matrix, dist_coeffs
)
# 각 마커에 대해 처리
for i in range(len(ids)):
# 좌표축 표시
cv2.drawFrameAxes(frame_undistorted, camera_matrix, dist_coeffs,
rvecs[i], tvecs[i], marker_size/2)
# 마커의 3D 위치 표시
pos_x = tvecs[i][0][0]
pos_y = tvecs[i][0][1]
pos_z = tvecs[i][0][2]
# 회전 벡터를 오일러 각도로 변환
rot_matrix, _ = cv2.Rodrigues(rvecs[i])
euler_angles = cv2.RQDecomp3x3(rot_matrix)[0]
# 마커 정보 표시
corner = corners[i][0]
center_x = int(np.mean(corner[:, 0]))
center_y = int(np.mean(corner[:, 1]))
cv2.putText(frame_undistorted,
f"ID: {ids[i][0]}",
(center_x, center_y - 40),
cv2.FONT_HERSHEY_SIMPLEX,
0.5, (0, 0, 0), 2)
cv2.putText(frame_undistorted,
f"Pos: ({pos_x:.2f}, {pos_y:.2f}, {pos_z:.2f})m",
(center_x, center_y),
cv2.FONT_HERSHEY_SIMPLEX,
0.5, (0, 0, 0), 2)
cv2.putText(frame_undistorted,
f"Rot: ({euler_angles[0]:.1f}, {euler_angles[1]:.1f}, {euler_angles[2]:.1f})deg",
(center_x, center_y + 20),
cv2.FONT_HERSHEY_SIMPLEX,
0.5, (0, 0, 0), 2)
# 코너 포인트 표시
for point in corner:
x, y = int(point[0]), int(point[1])
cv2.circle(frame_undistorted, (x, y), 4, (0, 0, 255), -1)
# 프레임 표시
cv2.imshow('ArUco Marker Detection', frame_undistorted)
# 'q' 키를 누르면 종료
if cv2.waitKey(1) & 0xFF == ord('q'):
break
# 리소스 해제
cap.release()
cv2.destroyAllWindows()
def main():
# 캘리브레이션 데이터 로드
try:
with open('camera_calibration.pkl', 'rb') as f:
calibration_data = pickle.load(f)
print("Calibration data loaded successfully")
except FileNotFoundError:
print("Error: Camera calibration file not found")
return
except Exception as e:
print(f"Error loading calibration data: {e}")
return
print("Starting ArUco marker detection...")
live_aruco_detection(calibration_data)
if __name__ == "__main__":
main()
카메라 캘리브레이션을 한 뒤 아루코마커를 검출하면 더 정확한 값을 얻을 수 있다. 그리고 위의 코드는 카메라 캘리브레이션을 적용했기 때문에, 코드를 원활히 작동하기 위해서는 이전 글을 참고하기 바란다.
더 정교한 아루코마커를 통한 위치 추정
위에서 보여준 사진은 아직 아루코마커의 기준 길이를 적용하지 않았을 때이다. 하지만 아래에서 보이는 사진과 코드는 아루코마커의 크기를 적용하였다. 즉, 아루코마커가 50mm임을 기준으로 각 아루코마커가 카메라의 중심으로 얼마나 떨어져 있고 회전되어 있는지 확인할 수 있다. 물론 아주 정확하지는 않지만, 약간의 오차를 가지고 위치와 회전 각도를 받을 수 있다.
import cv2
import numpy as np
import os
import time
import pickle
def live_aruco_detection(calibration_data):
"""
실시간으로 비디오를 받아 ArUco 마커를 검출하고 3D 포즈를 추정하는 함수
Args:
calibration_data: 카메라 캘리브레이션 데이터를 포함한 딕셔너리
- camera_matrix: 카메라 내부 파라미터 행렬
- dist_coeffs: 왜곡 계수
"""
# 캘리브레이션 데이터 추출
camera_matrix = calibration_data['camera_matrix']
dist_coeffs = calibration_data['dist_coeffs']
# ArUco 검출기 설정
aruco_dict = cv2.aruco.getPredefinedDictionary(cv2.aruco.DICT_4X4_250)
aruco_params = cv2.aruco.DetectorParameters()
detector = cv2.aruco.ArucoDetector(aruco_dict, aruco_params)
# 마커 크기 및 3D 좌표 설정 (밀리미터 단위)
marker_size = 50 # 35mm
marker_3d_edges = np.array([
[0, 0, 0],
[0, marker_size, 0],
[marker_size, marker_size, 0],
[marker_size, 0, 0]
], dtype='float32').reshape((4, 1, 3))
# 색상 정의
blue_BGR = (255, 0, 0)
# 카메라 설정
cap = cv2.VideoCapture(0)
# 카메라 초기화 대기
time.sleep(2)
while True:
ret, frame = cap.read()
if not ret:
print("Failed to grab frame")
break
# 이미지 왜곡 보정
frame_undistorted = cv2.undistort(frame, camera_matrix, dist_coeffs)
# 마커 검출
corners, ids, rejected = detector.detectMarkers(frame_undistorted)
# 마커가 검출되면 표시 및 포즈 추정
if corners:
for corner in corners:
# 코너 포인트 추출 및 표시
corner = np.array(corner).reshape((4, 2))
(topLeft, topRight, bottomRight, bottomLeft) = corner
# 코너 포인트 좌표 변환
topRightPoint = (int(topRight[0]), int(topRight[1]))
topLeftPoint = (int(topLeft[0]), int(topLeft[1]))
bottomRightPoint = (int(bottomRight[0]), int(bottomRight[1]))
bottomLeftPoint = (int(bottomLeft[0]), int(bottomLeft[1]))
# 코너 포인트 표시
cv2.circle(frame_undistorted, topLeftPoint, 4, blue_BGR, -1)
cv2.circle(frame_undistorted, topRightPoint, 4, blue_BGR, -1)
cv2.circle(frame_undistorted, bottomRightPoint, 4, blue_BGR, -1)
cv2.circle(frame_undistorted, bottomLeftPoint, 4, blue_BGR, -1)
# PnP로 포즈 추정
ret, rvec, tvec = cv2.solvePnP(
marker_3d_edges,
corner,
camera_matrix,
dist_coeffs
)
if ret:
# 위치 및 회전 정보 계산
x = round(tvec[0][0], 2)
y = round(tvec[1][0], 2)
z = round(tvec[2][0], 2)
rx = round(np.rad2deg(rvec[0][0]), 2)
ry = round(np.rad2deg(rvec[1][0]), 2)
rz = round(np.rad2deg(rvec[2][0]), 2)
# 위치 및 회전 정보 표시
pos_text = f"Pos: ({x}, {y}, {z})mm"
rot_text = f"Rot: ({rx}, {ry}, {rz})deg"
cv2.putText(frame_undistorted,
pos_text,
(int(topLeft[0]-10), int(topLeft[1]+10)),
cv2.FONT_HERSHEY_SIMPLEX,
0.5, (0, 0, 255), 2)
cv2.putText(frame_undistorted,
rot_text,
(int(topLeft[0]-10), int(topLeft[1]+40)),
cv2.FONT_HERSHEY_SIMPLEX,
0.5, (0, 0, 255), 2)
# 좌표축 표시
cv2.drawFrameAxes(frame_undistorted, camera_matrix, dist_coeffs,
rvec, tvec, marker_size/2)
# 프레임 표시
cv2.imshow('ArUco Marker Detection', frame_undistorted)
# 'q' 키를 누르면 종료
if cv2.waitKey(1) & 0xFF == ord('q'):
break
# 리소스 해제
cap.release()
cv2.destroyAllWindows()
def main():
# 캘리브레이션 데이터 로드
try:
with open('camera_calibration.pkl', 'rb') as f:
calibration_data = pickle.load(f)
print("Calibration data loaded successfully")
except FileNotFoundError:
print("Error: Camera calibration file not found")
return
except Exception as e:
print(f"Error loading calibration data: {e}")
return
print("Starting ArUco marker detection...")
live_aruco_detection(calibration_data)
if __name__ == "__main__":
main()
마무리
오늘은 OpenCV를 가지고 아루코마커를 인식하고, 아루코마커의 위치와 각도를 반환하는 알고리즘을 만들어 보았다. 아루코마커를 사용함으로써 딥러닝 모델을 만들지 않더라도 쉽고 간단하게 객체의 위치나 각도를 알 수 있다는 장점이 있다. 그래서 이번 프로젝트에서 아루코마커를 기준으로 두고, 인식한 상자가 얼마나 떨어져 있고, 회전되어 있는지 알려주는 알고리즘을 만들 예정이다.
'Robotics' 카테고리의 다른 글
[Ball Balancing] 1. 인벤터로 동작 시뮬레이션 확인하기 (1) | 2024.11.22 |
---|---|
[Ball Balancing] 0. 아두이노 프로젝트로 공 균형 잡기 도전 (1) | 2024.11.15 |
[OpenCV] 1. 카메라 캘리브레이션 (Camera Calibration) (1) | 2024.11.13 |
[OpenCV] 0. 로봇 비전을 활용한 상자 분류 프로젝트 (0) | 2024.11.12 |
[로봇공학] 3. Inverse Kinematics로 2DOF 로봇팔 계산하기 (0) | 2024.10.30 |