Files
aruco_ws/aruco_detector_hikrobot.py
T

168 lines
7.5 KiB
Python

import sys
import numpy as np
import cv2
import cv2.aruco as aruco
import math
from ctypes import *
# MVS 파이썬 SDK 경로 추가
sys.path.append("/opt/MVS/Samples/64/Python/MvImport")
from MvCameraControl_class import *
def rotation_matrix_to_quaternion(R):
"""
회전 행렬(Rotation Matrix)을 수치적으로 안정적인 쿼터니언(qx, qy, qz, qw)으로 변환
"""
tr = R[0, 0] + R[1, 1] + R[2, 2]
if tr > 0:
S = math.sqrt(tr + 1.0) * 2
qw = 0.25 * S
qx = (R[2, 1] - R[1, 2]) / S
qy = (R[0, 2] - R[2, 0]) / S
qz = (R[1, 0] - R[0, 1]) / S
elif (R[0, 0] > R[1, 1]) and (R[0, 0] > R[2, 2]):
S = math.sqrt(1.0 + R[0, 0] - R[1, 1] - R[2, 2]) * 2
qw = (R[2, 1] - R[1, 2]) / S
qx = 0.25 * S
qy = (R[0, 1] + R[1, 0]) / S
qz = (R[0, 2] + R[2, 0]) / S
elif R[1, 1] > R[2, 2]:
S = math.sqrt(1.0 + R[1, 1] - R[0, 0] - R[2, 2]) * 2
qw = (R[0, 2] - R[2, 0]) / S
qx = (R[0, 1] + R[1, 0]) / S
qy = 0.25 * S
qz = (R[1, 2] + R[2, 1]) / S
else:
S = math.sqrt(1.0 + R[2, 2] - R[0, 0] - R[1, 1]) * 2
qw = (R[1, 0] - R[0, 1]) / S
qx = (R[0, 2] + R[2, 0]) / S
qy = (R[1, 2] + R[2, 1]) / S
qz = 0.25 * S
if tr <= 0:
if (R[0, 0] > R[1, 1]) and (R[0, 0] > R[2, 2]): pass
elif R[1, 1] > R[2, 2]: qw = (R[0, 2] - R[2, 0]) / S
else: qw = (R[1, 0] - R[0, 1]) / S
return qx, qy, qz, qw
def main():
# 1. 카메라 초기화
deviceList = MV_CC_DEVICE_INFO_LIST()
tlayerType = MV_GIGE_DEVICE | MV_USB_DEVICE
ret = MvCamera.MV_CC_EnumDevices(tlayerType, deviceList)
if ret != 0:
print(f"카메라 검색 실패! ret[0x{ret:x}]")
return
if deviceList.nDeviceNum == 0:
print("연결된 카메라가 없습니다.")
return
stDeviceImgLimit = cast(deviceList.pDeviceInfo[0], POINTER(MV_CC_DEVICE_INFO)).contents
cam = MvCamera()
cam.MV_CC_CreateHandle(stDeviceImgLimit)
cam.MV_CC_OpenDevice(MV_ACCESS_Exclusive, 0)
cam.MV_CC_SetEnumValue("ExposureAuto", 0)
cam.MV_CC_SetFloatValue("ExposureTime", 50000.0)
cam.MV_CC_SetBoolValue("AcquisitionFrameRateEnable", True)
cam.MV_CC_SetFloatValue("AcquisitionFrameRate", 15.0)
ret = cam.MV_CC_StartGrabbing()
# 150mm 마커 크기 반영 (0.15미터)
marker_size = 0.15
# 캘리브레이션 파라미터 (1440x1080 규격 원본 파라미터)
fx, fy, cx, cy = 1203.078148, 1206.096396, 699.186863, 565.715472
mtx = np.array([[fx, 0, cx], [0, fy, cy], [0, 0, 1]])
dist = np.array([[-0.102740, 0.093985, -0.000759, -0.001804, 0.0]])
# 7x7 출력 마커 설정
dictionary = aruco.getPredefinedDictionary(aruco.DICT_7X7_50)
parameters = aruco.DetectorParameters()
try:
while True:
stOutFrame = MV_FRAME_OUT()
ret = cam.MV_CC_GetImageBuffer(stOutFrame, 1000)
if ret == 0:
if stOutFrame.pBufAddr:
addr = cast(stOutFrame.pBufAddr, c_void_p).value
nWidth = stOutFrame.stFrameInfo.nWidth
nHeight = stOutFrame.stFrameInfo.nHeight
nFrameLen = stOutFrame.stFrameInfo.nFrameLen
pData = (c_ubyte * nFrameLen).from_address(addr)
img_raw = np.frombuffer(pData, dtype=np.uint8).reshape(nHeight, nWidth)
frame = cv2.cvtColor(img_raw, cv2.COLOR_BayerGB2BGR)
# ArUco 인식
corners, ids, _ = aruco.detectMarkers(frame, dictionary, parameters=parameters)
if ids is not None:
# 꼭짓점 좌표 정밀도를 소수점 이하(Sub-pixel) 단위로 보정하는 연산
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 100, 0.001)
for corner in corners:
cv2.cornerSubPix(gray, corner, (5, 5), (-1, -1), criteria)
for i in range(len(ids)):
marker_id = ids[i][0]
rvec, tvec, _ = aruco.estimatePoseSingleMarkers(corners[i], marker_size, mtx, dist)
# 회전 변환 행렬 계산
rmat, _ = cv2.Rodrigues(rvec)
# 💡 [수정] OpenCV 기준계와 매칭하기 위한 X축 180도 회전 보정 행렬
R_flip = np.array([
[1, 0, 0],
[0, -1, 0],
[0, 0, -1]
], dtype=np.float32)
# 회전 행렬 교정 연산
rmat_corrected = np.dot(rmat, R_flip)
# 보정된 회전 행렬 기반으로 쿼터니언 변환 계산
qx, qy, qz, qw = rotation_matrix_to_quaternion(rmat_corrected)
# 시각화할 좌표축 벡터도 보정 처리
rvec_corrected, _ = cv2.Rodrigues(rmat_corrected)
# 마커 '정중앙' 원점 추출 및 수치 맵핑
tvec_center = tvec[0][0]
x, y, z = tvec_center[0], tvec_center[1], tvec_center[2]
# 시각화 (마커 테두리선 및 3축 기둥 렌더링)
aruco.drawDetectedMarkers(frame, corners)
cv2.drawFrameAxes(frame, mtx, dist, rvec_corrected, tvec_center, 0.05)
# 텍스트 출력 위치 설정
text_pos = (int(corners[i][0][0][0]), int(corners[i][0][0][1]) - 75)
# 텍스트 색상을 실제 시각화 축 색상과 1:1 일치 (BGR 포맷 기준)
cv2.putText(frame, f"ID:{marker_id}", text_pos, cv2.FONT_HERSHEY_SIMPLEX, 0.45, (255, 255, 255), 2)
cv2.putText(frame, f"X:{x:.2f}m (Red)", (text_pos[0], text_pos[1] + 20), cv2.FONT_HERSHEY_SIMPLEX, 0.45, (0, 0, 255), 2)
cv2.putText(frame, f"Y:{y:.2f}m (Green)", (text_pos[0], text_pos[1] + 40), cv2.FONT_HERSHEY_SIMPLEX, 0.45, (0, 255, 0), 2)
cv2.putText(frame, f"Z:{z:.2f}m (Blue)", (text_pos[0], text_pos[1] + 60), cv2.FONT_HERSHEY_SIMPLEX, 0.45, (255, 0, 0), 2)
# 수치 안정성이 확보된 쿼터니언 정보 출력
quat_text = f"Q [x:{qx:.3f}, y:{qy:.3f}, z:{qz:.3f}, w:{qw:.3f}]"
cv2.putText(frame, quat_text, (text_pos[0], text_pos[1] + 80), cv2.FONT_HERSHEY_SIMPLEX, 0.42, (0, 255, 255), 2)
cv2.imshow("Hikrobot ArUco V2 (Pose with Units)", frame)
cam.MV_CC_FreeImageBuffer(stOutFrame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
finally:
cam.MV_CC_StopGrabbing()
cam.MV_CC_CloseDevice()
cam.MV_CC_DestroyHandle()
cv2.destroyAllWindows()
if __name__ == "__main__":
main()