opencv를 활용해 작성한 객체 검출 코드 중 일부분들을 정리하려고 한다.
이미지 전 처리 과정
imgGray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
imgBlur = cv2.GaussianBlur(imgGray, (5,5), 1)
imgCanny = cv2.Canny(imgBlur, 100, 250)
kernel = np.ones((5, 5))
imgDial = cv2.dilate(imgCanny, kernel, iterations=4)
imgThre = cv2.erode(imgDial, kernel, iterations=3)
Original: 원본 이미지를 준비한다. 이 이미지는 컬러로 되어 있으며, 처리하기 전에 가장 기본이 되는 데이터이다.
Gray scale: 컬러 이미지를 그레이스케일로 변환한다. 이 단계는 색상을 단순화하여 계산량을 줄이고, 다음 단계에서의 처리 효율을 높이기 위해 필요하다.
Blur: 그레이스케일 이미지에 가우시안 블러를 적용한다. 블러 처리는 이미지의 노이즈를 줄이고, 엣지 검출 시 불필요한 세부사항을 제거하여 더 명확한 결과를 얻기 위해 수행된다.
Canny Edge: 블러 처리된 이미지에 Canny 엣지 검출 알고리즘을 적용한다. 이 단계에서는 이미지 내의 경계선을 찾아내어 중요한 형태와 구조를 파악한다.
Final: 엣지 검출된 이미지를 팽창(dilation)하고 침식(erosion) 처리하여 최종 결과물을 얻는다. 팽창은 엣지의 두께를 증가시켜 연결된 부분을 강조하고, 침식은 노이즈를 제거하여 엣지를 더 명확하게 만든다.
이미지 변형

def warpImg (img,points,w,h,pad=20):
points =reorder(points)
pts1 = np.float32(points)
pts2 = np.float32([[0,0],[w,0],[0,h],[w,h]])
matrix = cv2.getPerspectiveTransform(pts1,pts2)
imgWarp = cv2.warpPerspective(img,matrix,(w,h))
imgWarp = imgWarp[pad:imgWarp.shape[0]-pad,pad:imgWarp.shape[1]-pad]
return imgWarp
Perspective transform: 원본 이미지를 투시 변환하여 새로운 시점에서 보이는 것처럼 변형한다. 이미지의 네 모서리 점을 기준으로 새로운 위치로 매핑한다.
warpImg 함수는 주어진 이미지와 변환할 네 개의 점을 받아 투시 변환을 수행한다. 입력된 점들을 재정렬하고, 변환 매트릭스를 계산한 후, 이미지를 새로운 시점으로 변환한다. 마지막으로, 변환된 이미지에서 패딩을 제거하여 최종 결과를 반환한다.
이 과정을 통해 원본 이미지의 시점을 변화시킬 수 있으며, 객체의 위치나 모양을 더 정확하게 분석할 수 있다.
객체 검출 및 형태 분석
contours, hierarchy = cv2.findContours(imgThre, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
finalCountours = []
for ni,i in enumerate(contours):
area = cv2.contourArea(i)
if area > minArea:
peri = cv2.arcLength(i, True)
approx = cv2.approxPolyDP(i, 0.04 * peri, True)
shape = "Unknown"
bbox=[]
btri=[]
bcir=[]
de=0
if len(approx) == 3:
shape = "Triangle"
btri=cv2.minEnclosingTriangle(approx)
bmtri=cv2.minAreaRect(approx)
de=bmtri[2]
elif len(approx) > 4 :
circularity = (4 * math.pi * area) / (peri * peri)
shape = "Circle" if circularity > 0.8 else "none"
bcir=cv2.minEnclosingCircle(approx)
elif len(approx)==4:
shape = "Rectangle"
bbox = cv2.boundingRect(approx)
bmbox=cv2.minAreaRect(approx)
de=bmbox[2]
finalCountours.append([len(approx), area, approx, bbox, btri, bcir,de,i,shape,ni])
finalCountours = sorted(finalCountours, key=lambda x: x[1], reverse=True)
먼저, 전처리된 이미지에서 외곽선을 검출한다. 이를 위해 cv2.findContours 함수를 사용하여 외곽선을 찾는다.
이제 검출된 외곽선을 순회하면서 각 외곽선의 면적을 계산하고, 최소 면적 기준을 넘는 외곽선만을 선택한다. 선택된 외곽선에 대해 다양한 형태 분석을 수행한다.
면적이 기준치 이상인 외곽선에 대해 둘레를 계산하고, 근사 다각형을 찾는다. 근사 다각형의 꼭짓점 수에 따라 형태를 결정한다.
- Triangle: 꼭짓점이 3개인 경우 삼각형으로 인식한다. 최소 외접 삼각형과 최소 면적 삼각형을 계산하여 방향 정보를 얻는다.
- Circle: 꼭짓점이 4개 초과인 경우 원으로 인식한다. 원형성을 계산하여 일정 값 이상일 때 원으로 판단하고, 최소 외접 원을 계산한다.
- Rectangle: 꼭짓점이 4개인 경우 사각형으로 인식한다. 최소 외접 사각형을 계산하여 방향 정보를 얻는다.
최종적으로, 각 객체의 형태, 면적, 외곽선, 경계 상자, 외접 도형, 방향 등의 정보를 리스트에 저장한다.
객체 정보 저장 함수
def save_all_contour_info(file_path, contours_info):
header = "인식번호, 모양, 중심x좌표, 중심y좌표, 기울기, 변의 길이\n"
contours_info = sorted(contours_info, key=lambda x: x[0])
# 파일을 새로 쓰기 모드로 열고, 헤더와 현재 감지된 모든 객체의 정보를 씀
with open(file_path, "w") as file:
file.write(header)
for contour in contours_info:
contour_id, shape, center_x, center_y, angle, lengths = contour
if isinstance(lengths, (list, tuple)):
lengths_str = ", ".join(map(str, lengths))
else:
lengths_str = str(lengths)
contour_data = f"{contour_id}, {shape}, {center_x}, {center_y}, {angle}, {lengths_str}\n"
file.write(contour_data)
file.write("\n")
file.close()
save_all_contour_info 함수는 주어진 파일 경로와 객체 정보를 받아, 이를 파일에 저장하는 역할을 한다. 객체 정보는 인식번호, 모양, 중심 좌표, 기울기, 변의 길이 등으로 구성된다.
먼저, 헤더를 정의하고 객체 정보를 인식번호 기준으로 정렬한 후 ,파일을 새로 쓰기 모드로 열고, 헤더와 함께 객체 정보를 파일에 작성한다.
중심점 계산 함수
def findCenter(nPoints,scale=1):
# nPoints 배열에서 꼭짓점 좌표 추출
x_coords = [point[0][0] for point in nPoints]
y_coords = [point[0][1] for point in nPoints]
# x, y 좌표의 평균을 구하여 중심점 계산
cx,dx = sum(x_coords) / len(x_coords)+20,sum(x_coords) / len(x_coords)+20
cy,dy = sum(y_coords) / len(y_coords)+20,sum(y_coords) / len(y_coords)+20
#print("Center of Rectangle: (", cx, ",", cy, ")")
return cx,cy,dx,dy
두 점 사이의 거리 계산 함수
def findDis(pts1,pts2):
return ((pts2[0]-pts1[0])**2 + (pts2[1]-pts1[1])**2)**0.5
실행 결과
과정을 좀 자세하게 기록하려고 했는데 거듭되는 실패 때문에 글 쓰기는 좀 미루고 개발에만 집중했다.
각도 측정은 여러 방법을 시도하다가 현재 방법으로 일단락했지만 아직 불안정하고 후에 수정이 필요하다.
'Project > 일지' 카테고리의 다른 글
Gazebo-ROS-UR5 환경 세팅 (0) | 2024.05.17 |
---|---|
NVIDIA드라이버 재설치 (0) | 2024.05.17 |
Opencv로 꼭짓점 코너 검출 (0) | 2024.04.18 |
ROS Noetic - Gazebo11 세팅 (0) | 2024.04.16 |
ROS Noetic 설치 (0) | 2024.04.11 |