1. 박스이미지 배경 제거
박스만 남기고, 나머지 배경을 제거 하기 위해, OpenCV의 여러 메소드를 사용했다.
# 이미지 크기 얻기
height, width = input.shape[:2]
# 관심 영역의 사각형 지정 (중앙 부근에 충분한 공간을 포함)
margin = 20 # 주변 공간 크기
rect = (margin, margin, width - 2 * margin, height - 2 * margin)
먼저, 이미지 중앙 부근에 박스가 항상 놓인다고 가정하고, 관심 영역을 지정한다.
# 초기 마스크 생성
mask = np.zeros(input.shape[:2], dtype=np.uint8)
# GrabCut 실행
bgdModel = np.zeros((1, 65), np.float64)
fgdModel = np.zeros((1, 65), np.float64)
cv2.grabCut(input, mask, rect, bgdModel, fgdModel, 5, cv2.GC_INIT_WITH_RECT)
# 결과 마스크에서 가능성 있는 전경/배경 픽셀 선택
mask2 = np.where((mask == 2) | (mask == 0), 0, 1).astype('uint8')
# 이미지에 마스크 적용하여 전경 추출
result = input * mask2[:, :, np.newaxis]
result = cv2.cvtColor(result, cv2.COLOR_BGR2GRAY)
이후 마스크를 생성하고, grabCut 메소드를 실행해서, 배경이 제거된 박스 이미지만 뽑아냈다.
2. 박스이미지 윤곽선 추출
박스의 꼭지점들(총 6개)을 쉽게 뽑아내기 위해, 윤곽선 이미지가 필요하다.
# 윤곽선만 표시된 이미지
nuki = cv2.Canny(result, threshold1=100, threshold2=250)
# 명암비 alpha 0이면 그대로, 양수일수록 명암비가 커진다.
alpha = 0.5
input = np.clip((1+alpha) * input - 128 * alpha, 0, 255).astype(np.uint8)
# morphology를 위한 kernel 제작 nxn의 kernel로 사각형(MORPH_RECT), 즉 커널이 전부 1로 채워짐
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3,3))
# 커널을 사용해 MORPH_CLOSE -> 커널에 맞게 주변 픽셀 다 선택해서 채우기 때문에 선이 두꺼워진다.
morphology = cv2.morphologyEx(nuki, cv2.MORPH_CLOSE, kernel)
Canny와 morphology 메소드를 이용해, 엄청 간단하게 추출해낼 수 있었다. threshold값은, 여러 데이터들을 가지고 실험을 통해, 가장 적절한 값을 사용했다.
3. 실제 계산
1. crop한 이미지 상의 좌표를 구함
2. 이미지 상의 상자 크기 구하기(이때, 상자 크기 정규화로 3D상에 표현, width=100으로 산정 후 대각선 길이와 이미지 밑부분으로 부터의 거리 곱)
3. 초점거리, 원본사진의 중점, 2D좌표, 이미자 상의 박스 크기를 이용해 임의로 정한 3D좌표를 이용해 카메라 외부 파라미터(rvec, tvec)을 구함
4. Rodrigues(rvec)으로 진짜 카메라 정보를 얻은 후, 실제 월드 좌표계의 박스 한 점과, 카메라의 좌표를 구한후 둘 사이 거리 구함
5. 카메라 초점거리 : 실제 카메라와 물체간 거리 = 이미지 상 박스 크기 : 실제 박스크기 비례식을 이용해 박스 크기 산출
6. 정규화 과정을 거쳤기 때문에, 실제 박스 크기로 변환하기 위한 상수값 곱하기
실제 계산을 위한 알고리즘이다. 각 단계에 대해 설명하려고 한다.
crop한 이미지 상의 좌표를 먼저 구한다. 좌표를 구하기 위해, y값이 가장 큰값과 작은값을 가진 픽셀(top, bottom)을 뽑고,
나머지 4개의 점은 이런식으로 직선 그래프 식을 통해 계산해낸다. 기울기는 여러 데이터들로 실험을 통해 적당한 값으로 책정했다.
이미지 상의 상자의 크기를 구한다. 단순히 위에서 구해낸 좌표들을 통해, 점과 점사이의 거리를 구하는 계산식을 썼고, 카메라 외부 파라미터를 구하기 위해, 2D상의 좌표 정보 뿐만 아니라, 3D상의 좌표 정보도 필요하기 때문에, 일단 width = 100으로 정규화를 시킨뒤, 나중에 복구해줄것이다.
def calculate_parameters(fx, fy, cx, cy, dist, top, bottom, left_top, left_bottom, right_top, right_bottom, width, height, tall):
"""
외부 파라미터 추정
"""
#2D 이미지 좌표
image_points = np.array([[bottom[0], bottom[1]],
[left_bottom[0], left_bottom[1]],
[right_bottom[0], right_bottom[1]],
[left_top[0], left_top[1]],
[right_top[0], right_top[1]],
[top[0], top[1]]],
dtype=np.float32)
#3D 좌표계에 생성한 박스 좌표
object_points = np.array([[0, 0, 0],
[height, 0, 0],
[0, width, 0],
[width, 0, tall],
[0, height, tall],
[width, height, tall]],
dtype=np.float32)
cameraMatrix = np.array([[fx, 0, cx],
[0, fy, cy],
[0, 0, 1]],
dtype=np.float32)
return cv2.solvePnP(object_points, image_points, cameraMatrix, dist, flags=cv2.SOLVEPNP_ITERATIVE)
카메라 외부 파라미터를 구해내는 과정이다. solvePnP 메소드를 통해 간단히 계산해 냈고, 이 계산을 하기 위해, 앞의 모든 과정이 필요하다고 봐도 무방하다. 이 계산을 통해, 카메라가 물체로부터 어느정도 떨어져있는지, 거리벡터(tvec)값을 얻게 된다.
이후 카메라-박스 간 거리는,
https://darkpgmr.tistory.com/153
영상의 기하학적 해석 - 영상의 지면 투영(ground projection)
카메라 영상에서 사람이나 자동차를 찾았을 때, 찾은 물체가 카메라로부터 실제로 얼마나 멀리 떨어져 있고 또 키는 얼마나 큰지 알아내는 것은 영상기하학의 가장 빈번한 응용 중 하나이다. 예
darkpgmr.tistory.com
이 포스팅의 3. 3D변환을 이용한 방법 을 통해 계산했다.
#카메라와 거리 : 초점거리 = 실제 박스크기 : 이미지상 박스크기
real_width = round((img_width) * distance / fx, 2)
real_height = round((height * (img_width / (100))) * distance / fx, 2)
real_tall = round((tall * (img_width / (100))) * distance / fx, 2)
constant = fx * 2.4
correction = constant / distance
real_width *= correction
real_height *= correction
real_tall *= correction
마지막으로 카메라와 박스간 거리 : 초점거리 = 실제 박스크기 : 이미지 상 박스크기 비례식을 활용해서, 계산을 마쳐준다. 이때, 앞에서 정규화 과정을 거쳤기 때문에 복구를 해 주고, 실제 박스 크기를 맞춰주기 위해, 여러 데이터를 통해 얻어낸 상수값을 곱해주면 된다.
여기까지 나와 팀이 구현한 알고리즘에 대한 설명이였다. 다음 포스팅에는, 참가 후기와 느낀점, 좋았거나 아쉬웠던 점을 써보려고 한다.
'프로젝트' 카테고리의 다른 글
공모전 회고록 (4) - CJ대한통운 미래기술 챌린지 2023 (0) | 2024.08.10 |
---|---|
공모전 회고록 (2) - CJ대한통운 미래기술 챌린지 2023 (1) | 2024.01.08 |
공모전 회고록 (1) - CJ대한통운 미래기술 챌린지 2023 (0) | 2023.11.21 |
두번째 개인 프로젝트 - 랜덤 로드뷰 게임 (Open Api 활용) (0) | 2023.06.07 |
첫 개인 프로젝트 - Black Jack (블랙잭 게임 JS 구현) (0) | 2023.03.21 |