solvePnP 함수 사용solvePnP 함수 사용목적: 이미지에서 노이즈를 제거하고 선명한 엣지를 검출하기 위해 전처리합니다.
구현 방법:
이미지를 그레이스케일로 변환합니다.코드 예제:
import cv2
# 이미지 로드
image = cv2.imread('field_image.jpg')
# 그레이스케일 변환
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# 가우시안 블러 적용
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
목적: 조명 조건이 불균일한 경우 조명 보정을 통해 엣지 검출 성능을 향상시킵니다.
구현 방법:
코드 예제:
# 히스토그램 이퀄라이제이션
equ = cv2.equalizeHist(blurred)
목적: 이미지에서 엣지를 검출하여 라인 검출의 기반을 마련합니다.
구현 방법:
Canny 함수를 사용하여 엣지를 검출합니다.코드 예제:
# Canny 엣지 검출
edges = cv2.Canny(blurred, threshold1=50, threshold2=150)
목적: 검출된 엣지로부터 직선을 검출합니다.
구현 방법:
코드 예제:
import numpy as np
# Hough Line Transform 적용
lines = cv2.HoughLinesP(
edges,
rho=1,
theta=np.pi / 180,
threshold=80,
minLineLength=50,
maxLineGap=5
)
목적: 검출된 라인 중에서 풋살장의 필드 라인만을 선택합니다.
구현 방법:
코드 예제:
def calculate_slope(line):
x1, y1, x2, y2 = line[0]
if x2 - x1 == 0:
return np.inf # 수직선
return (y2 - y1) / (x2 - x1)
filtered_lines = []
for line in lines:
slope = calculate_slope(line)
length = np.hypot(line[0][2] - line[0][0], line[0][3] - line[0][1])
# 기울기와 길이로 필터링
if -0.1 < slope < 0.1 or slope > 10 or slope < -10:
if length > 100: # 최소 길이 조건
filtered_lines.append(line)
목적: 필드의 코너, 페널티 에어리어 등 중요한 지점을 교차점으로 찾습니다.
구현 방법:
코드 예제:
def line_intersection(line1, line2):
x1, y1, x2, y2 = line1[0]
x3, y3, x4, y4 = line2[0]
# 두 직선의 방정식 계산
denominator = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4)
if denominator == 0:
return None # 평행선
px = ((x1*y2 - y1*x2)*(x3 - x4) - (x1 - x2)*(x3*y4 - y3*x4)) / denominator
py = ((x1*y2 - y1*x2)*(y3 - y4) - (y1 - y2)*(x3*y4 - y3*x4)) / denominator
return [px, py]
# 교차점 계산
intersections = []
for i in range(len(filtered_lines)):
for j in range(i+1, len(filtered_lines)):
pt = line_intersection(filtered_lines[i], filtered_lines[j])
if pt is not None:
x, y = pt
if 0 <= x <= image.shape[1] and 0 <= y <= image.shape[0]:
intersections.append(pt)
목적: 필드의 센터 서클을 검출하여 추가적인 특징점으로 사용합니다.
구현 방법:
HoughCircles 함수를 사용하여 원을 검출합니다.코드 예제:
# 원 검출을 위한 이미지 전처리 (엣지 강조)
circles_gray = cv2.medianBlur(gray, 5)
circles_edges = cv2.Canny(circles_gray, 50, 150)
# Hough Circle Transform 적용
circles = cv2.HoughCircles(
circles_edges,
cv2.HOUGH_GRADIENT,
dp=1,
minDist=100,
param1=50,
param2=30,
minRadius=10,
maxRadius=100
)
# 검출된 원 확인
if circles is not None:
circles = np.uint16(np.around(circles))
for i in circles[0, :]:
# 원 그리기 (중심 좌표: i[0], i[1], 반지름: i[2])
cv2.circle(image, (i[0], i[1]), i[2], (0, 255, 0), 2)
# 중심점 저장
intersections.append([i[0], i[1]])
목적: 이미지에서 검출된 특징점을 실제 필드의 세계 좌표와 매칭하여 대응점을 생성합니다.
구현 방법:
코드 예제:
# 검출된 특징점 시각화
for pt in intersections:
cv2.circle(image, (int(pt[0]), int(pt[1])), 5, (0, 0, 255), -1)
cv2.imshow('Intersections', image)
cv2.waitKey(0)
cv2.destroyAllWindows()
매칭 예시:
[[x_img1, y_img1], [x_img2, y_img2], ...][[x_world1, y_world1, z_world1], [x_world2, y_world2, z_world2], ...]주의사항:
목적: 대응점을 사용하여 카메라의 위치와 방향(외부 파라미터)을 추정합니다.
구현 방법:
solvePnP 함수를 사용합니다.코드 예제:
import cv2
import numpy as np
# 이미지 좌표 (앞서 검출된 특징점)
img_points = np.array([
[x_img1, y_img1],
[x_img2, y_img2],
# ...
], dtype=np.float32)
# 세계 좌표 (필드의 표준 치수에 따라 설정)
world_points = np.array([
[x_world1, y_world1, z_world1],
[x_world2, y_world2, z_world2],
# ...
], dtype=np.float32)
# 내부 파라미터 및 왜곡 계수
camera_matrix = ... # (3x3 행렬)
dist_coeffs = ... # 왜곡 계수
# 외부 파라미터 추정
success, rvec, tvec = cv2.solvePnP(
world_points,
img_points,
camera_matrix,
dist_coeffs,
flags=cv2.SOLVEPNP_ITERATIVE
)
목적: 추정된 외부 파라미터의 정확성을 검증합니다.
구현 방법:
코드 예제:
# 세계 좌표를 이미지 좌표로 투영
projected_img_points, _ = cv2.projectPoints(
world_points,
rvec,
tvec,
camera_matrix,
dist_coeffs
)
# 오차 계산
errors = img_points - projected_img_points.reshape(-1, 2)
squared_errors = np.square(errors)
mean_squared_error = np.mean(squared_errors)
print(f"Mean Squared Reprojection Error: {mean_squared_error}")
오차가 큰 경우:
앞서 설명한 대로, 검출된 외부 파라미터를 사용하여 선수들의 위치를 추정할 수 있습니다. 이 과정은 이전 답변에서 자세히 설명하였으므로 간략히 요약하겠습니다.
프로그램을 구성하기 위한 전체적인 코드 구조를 제안해드립니다.
def main():
# 1. 이미지 로드 및 전처리
image = cv2.imread('field_image.jpg')
preprocessed_image = preprocess_image(image)
# 2. 엣지 검출
edges = detect_edges(preprocessed_image)
# 3. 라인 검출
lines = detect_lines(edges)
# 4. 라인 필터링
filtered_lines = filter_lines(lines)
# 5. 라인 교차점 계산
intersections = compute_intersections(filtered_lines)
# 6. 원 검출
circles = detect_circles(preprocessed_image)
if circles is not None:
intersections.extend(circles)
# 7. 특징점 매칭
img_points, world_points = match_feature_points(intersections)
# 8. 카메라 외부 파라미터 추정
rvec, tvec = estimate_camera_pose(img_points, world_points, camera_matrix, dist_coeffs)
# 9. 검증 및 보정
validate_pose(rvec, tvec, img_points, world_points, camera_matrix, dist_coeffs)
# 10. 선수 검출 및 위치 추정
player_world_positions = detect_and_estimate_player_positions(image, rvec, tvec, camera_matrix, dist_coeffs)
# 11. 결과 시각화
visualize_results(player_world_positions)
if __name__ == "__main__":
main()
각 함수는 앞서 설명한 단계별 구현을 포함합니다.
threshold, minLineLength, maxLineGap 등을 조정해야 합니다.Canny 함수의 threshold1, threshold2 값을 조정하여 엣지 검출 결과를 최적화합니다.HoughCircles 함수의 param1, param2, minRadius, maxRadius 등을 조정합니다.자동 특징점 추출은 초기 설정과 파라미터 조정에 시간이 걸릴 수 있지만, 한번 설정되면 자동으로 처리할 수 있어 효율적입니다. 위의 단계별 설명과 코드를 참고하여 프로그램을 구현하시면 선수들의 위치를 담은 BEV 맵을 생성하실 수 있을 것입니다.
구현 중에 궁금한 점이나 추가 도움이 필요하시면 언제든지 문의해주세요!