BEV map 만들기

FSA·2024년 10월 2일

BEV

목록 보기
1/3

0. 진행해볼 과정

  • 아래 과정을 싱크가 맞는 이미지를 꺼내면서 반복할 것임 (data/left_frames , data/right_frames 에 각 프레임별 이미지가 있어. 싱크는 그들의 이름으로 맞춰져 있어. 0.png, 1.png, ...)
  1. 각 카메라 싱크가 맞는 이미지 2장을 열어서, 마우스로 특징점을 찍음 ( 다 찍고 나면 enter을 누르면 완료. 처음부터 다시 찍고 싶으면 r을 누르면 됨)
  2. 특징점 매칭
    • 검출된 특징점을 실제 필드의 세계 좌표와 매칭
    • 구현 방식은, 마우스로 점을 찍을 때마다, (x,y) 좌표를 입력할 수 있게 코드 구현
  3. 카메라 외부 파라미터 추정
  • OpenCV의 solvePnP 함수 사용
  1. human detection 딥러닝 알고리즘으로 사람 박스 검출
  2. 사람 박스의 밑변이 지면(z=0)에 닿아있다고 가정하고, 사람의 x,y 위치를 추출 (중심점과 반지름 길이를 추출하면 됨)
  3. 데이터 융합 및 BEV 맵 생성
  • 두 카메라에서 얻은 선수 위치 정보를 통합하여 하나의 BEV 맵을 생성합니다.
  • 중복된 선수는 하나로 합치고, 전체 필드의 선수 위치를 파악합니다.
  • scale을 변수로 조절할 수 있게 합니다.
  1. 각 쌍의 BEV map 결과를 results 폴더에 저장합니다.
  • results/not_merged 폴더에, 중복된 선수를 하나로 합치기 전 BEV map을 저장합니다.
  • results/merged 폴더에, 중복된 선수를 하나로 합친 BEV map을 저장합니다.
  • 최종적으로는 BEV map을 하나의 동영상으로 만듭니다.

1. 필드의 특징점 자동으로 추출하기

1.1. 전체 프로세스 개요

  1. 이미지 전처리
    • 색상 변환 및 노이즈 제거
  2. 엣지 검출
    • Canny Edge Detection 사용
  3. 라인 검출
    • Hough Line Transform 사용
  4. 라인 필터링
    • 필드 라인만을 선택하기 위한 기준 설정
  5. 라인 교차점 계산
    • 검출된 라인들의 교차점을 찾아 특징점으로 사용
  6. 원 검출
    • Hough Circle Transform을 사용하여 센터 서클 검출
  7. 특징점 매칭
    • 검출된 특징점을 실제 필드의 세계 좌표와 매칭
  8. 카메라 외부 파라미터 추정
    • OpenCV의 solvePnP 함수 사용
  9. 검증 및 보정
    • 투영 오차 계산 및 보정

1. 이미지 전처리

1.1 색상 변환 및 노이즈 제거

목적: 이미지에서 노이즈를 제거하고 선명한 엣지를 검출하기 위해 전처리합니다.

구현 방법:

  • 이미지를 그레이스케일로 변환합니다.
  • 가우시안 블러(Gaussian Blur)를 사용하여 노이즈를 줄입니다.

코드 예제:

import cv2

# 이미지 로드
image = cv2.imread('field_image.jpg')

# 그레이스케일 변환
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

# 가우시안 블러 적용
blurred = cv2.GaussianBlur(gray, (5, 5), 0)

1.2 조명 보정 (선택 사항)

목적: 조명 조건이 불균일한 경우 조명 보정을 통해 엣지 검출 성능을 향상시킵니다.

구현 방법:

  • 어댑티브 이퀄라이제이션(Adaptive Equalization) 적용

코드 예제:

# 히스토그램 이퀄라이제이션
equ = cv2.equalizeHist(blurred)

2. 엣지 검출

2.1 Canny Edge Detection 사용

목적: 이미지에서 엣지를 검출하여 라인 검출의 기반을 마련합니다.

구현 방법:

  • OpenCV의 Canny 함수를 사용하여 엣지를 검출합니다.
  • 임계값은 이미지에 따라 조정합니다.
    • TODO: 임계값을 어떻게 조정하는게 좋을지?

코드 예제:

# Canny 엣지 검출
edges = cv2.Canny(blurred, threshold1=50, threshold2=150)

3. 라인 검출

3.1 Hough Line Transform 사용

목적: 검출된 엣지로부터 직선을 검출합니다.

구현 방법:

  • 확률적 허프 변환(Probabilistic Hough Transform)을 사용하여 라인을 검출합니다.
  • 최소 길이와 최대 허용 간격 등의 파라미터를 조정합니다.

코드 예제:

import numpy as np

# Hough Line Transform 적용
lines = cv2.HoughLinesP(
    edges,
    rho=1,
    theta=np.pi / 180,
    threshold=80,
    minLineLength=50,
    maxLineGap=5
)

4. 라인 필터링

4.1 필드 라인만 선택하기 위한 기준 설정

목적: 검출된 라인 중에서 풋살장의 필드 라인만을 선택합니다.

구현 방법:

  • 라인의 기울기와 길이를 기반으로 필터링합니다.
  • 수평 및 수직 라인을 우선적으로 선택합니다.
  • 필드 라인의 예상 기울기 범위를 설정합니다.

코드 예제:

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)

5. 라인 교차점 계산

5.1 라인 간 교차점 찾기

목적: 필드의 코너, 페널티 에어리어 등 중요한 지점을 교차점으로 찾습니다.

구현 방법:

  • 각 라인 간의 교차점을 계산합니다.
  • 교차점이 이미지 내에 존재하는지 확인합니다.

코드 예제:

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)

6. 원 검출

6.1 Hough Circle Transform 사용

목적: 필드의 센터 서클을 검출하여 추가적인 특징점으로 사용합니다.

구현 방법:

  • OpenCV의 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]])

7. 특징점 매칭

7.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], ...]

주의사항:

  • 자동 매칭은 어려울 수 있으므로, 초기에는 수동으로 매칭하여 정확한 대응점을 확보합니다.
  • 특징점의 위치와 필드의 구조를 고려하여 어떤 이미지 점이 어떤 세계 좌표에 대응하는지 결정합니다.

8. 카메라 외부 파라미터 추정

8.1 PnP 알고리즘 사용

목적: 대응점을 사용하여 카메라의 위치와 방향(외부 파라미터)을 추정합니다.

구현 방법:

  • OpenCV의 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
)

9. 검증 및 보정

9.1 투영 오차 계산

목적: 추정된 외부 파라미터의 정확성을 검증합니다.

구현 방법:

  • 세계 좌표를 이미지 좌표로 투영하여 원래의 이미지 좌표와 비교합니다.
  • 오차를 계산하여 평균 오차가 허용 범위 내인지 확인합니다.

코드 예제:

# 세계 좌표를 이미지 좌표로 투영
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}")

9.2 보정 방법

  • 오차가 큰 경우:

    • 특징점 매칭을 재검토합니다.
    • 이상치(outlier)를 제거합니다.
    • 더 많은 특징점을 사용하여 정확도를 높입니다.

10. 선수 검출 및 위치 추정

앞서 설명한 대로, 검출된 외부 파라미터를 사용하여 선수들의 위치를 추정할 수 있습니다. 이 과정은 이전 답변에서 자세히 설명하였으므로 간략히 요약하겠습니다.

10.1 선수 검출

  • 딥러닝 기반의 객체 검출 모델(YOLO 등)을 사용하여 선수들을 검출합니다.

10.2 이미지 좌표 추출

  • 검출된 선수의 바운딩 박스 중심 좌표를 계산합니다.

10.3 세계 좌표로 변환

  • 추정된 외부 파라미터를 사용하여 이미지 좌표를 세계 좌표로 변환합니다.
  • 선수들은 지면(Z=0)에 있으므로, Z=0 평면과의 교점을 계산합니다.

11. 전체 코드 구조 제안

프로그램을 구성하기 위한 전체적인 코드 구조를 제안해드립니다.

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()

각 함수는 앞서 설명한 단계별 구현을 포함합니다.


추가 팁 및 고려 사항

1. 파라미터 조정

  • 허프 변환 파라미터: 이미지에 따라 threshold, minLineLength, maxLineGap 등을 조정해야 합니다.
  • 엣지 검출 임계값: Canny 함수의 threshold1, threshold2 값을 조정하여 엣지 검출 결과를 최적화합니다.
  • 원 검출 파라미터: HoughCircles 함수의 param1, param2, minRadius, maxRadius 등을 조정합니다.

2. 성능 향상

  • ROI 설정: 필드 영역만을 대상으로 처리하여 연산량을 줄입니다.
  • 멀티프로세싱: 큰 이미지나 영상 처리 시 멀티프로세싱을 활용합니다.

3. 예외 처리

  • 검출된 라인이나 원이 없는 경우를 대비하여 예외 처리를 구현합니다.
  • 특징점 매칭 시 매칭되지 않는 점들이 있을 수 있으므로 이에 대한 처리를 합니다.

4. 검출 결과 시각화

  • 중간 결과를 시각화하여 각 단계의 결과를 확인합니다.
  • 검출된 라인, 교차점, 원 등을 이미지에 그려 확인합니다.

결론

자동 특징점 추출은 초기 설정과 파라미터 조정에 시간이 걸릴 수 있지만, 한번 설정되면 자동으로 처리할 수 있어 효율적입니다. 위의 단계별 설명과 코드를 참고하여 프로그램을 구현하시면 선수들의 위치를 담은 BEV 맵을 생성하실 수 있을 것입니다.

구현 중에 궁금한 점이나 추가 도움이 필요하시면 언제든지 문의해주세요!

profile
모든 의사 결정 과정을 지나칠 정도로 모두 기록하고, 나중에 스스로 피드백 하는 것

0개의 댓글