컴퓨터 비전
파이썬은 배열 처리에 유리하며, numpy는 다차원 배열을 위한 모듈이다.
OpenCV는 영상을 numpy.ndarray로 표현한다.
설치 방법
python -m pip install opencv-python
색상 공 탐지 및 인식 알고리즘
1. 이미지 데이터 생성
- 그림판이나 OpenCV를 이용하여 빨간색, 파란색, 녹색의 원을 5개 이상 만든다.
- 영상의 크기는 640 x 480 으로 함
우리 팀원중에 한명이 아래와 같이 이미지를 만들어줬다.
2. 객체 탐지 및 인식
- 1번 이미지에서 생성된 원을 탐지 및 인식함
- 탐지된 원에는 사각형 박스를 씌어주고, 글자 출력하여 색상 정보를 글자로 알려줌
그렇다면, 어떻게 원을 탐지하고, 그 색상을 인지해서 알려줄까?
다양한 접근 방법
1. R, G, B 채널을 각각 따로 해서, 원을 탐지하고, 나중에 합치기
2. R, G, B의 채널을 함께 OpenCV의 HoughCircles 함수를 활용하여 원 인식 후, 색상 text 넣기
Python+OpenCV] 이미지에서 원형 도형 검출(Hough Circle Transform)
Hough Transform 알고리즘은, 수학적 모델링이 가능한 모든 도형을 이미지에서 검출할 수 있는 방법이다.
원형에 수학적 모델 식을 이용해 허프 변환(Hough Transform)을 적용할 수 있는데, 원에 대한 수학식이 중심점(x, y)와 반지름(r)이라는 3개의 매개변수로 구성되어 있고, 결국 3차원 배열이라는 저장소 요구 및 연산이 비효율적이다.
이에 대한 개선으로 Gradient(가장자리에서의 기울기값)을 이용하여 Hough Transform 을 적용할 수 있어 허브 그래디언트 방법을 사용한다. OpenCV에서는 cv2.HoughCircles 함수를 제공한다.
아래는 OpenCV에서의 cv2.HoughCircles 함수의 예시이다.
import numpy as np
import cv2 as cv
img = cv.imread('[이미지_주소]', cv.IMREAD_GRAYSCALE)
assert img is not None, "file could not be read, check with os.path.exists()"
img = cv.medianBlur(img,5)
cimg = cv.cvtColor(img,cv.COLOR_GRAY2BGR)
circles = cv.HoughCircles(img,cv.HOUGH_GRADIENT,1,20,
param1=50,param2=30,minRadius=0,maxRadius=0)
circles = np.uint16(np.around(circles))
for i in circles[0,:]:
# draw the outer circle
cv.circle(cimg,(i[0],i[1]),i[2],(0,255,0),2)
# draw the center of the circle
cv.circle(cimg,(i[0],i[1]),2,(0,0,255),3)
cv.imshow('detected circles',cimg)
cv.waitKey(0)
cv.destroyAllWindows()
그러면 아래와 같이 결과값이 나온다.
cv2.HoughCircles 함수 인자
- 첫번째 : 입력 이미지로서, 8비트 단일 채널인 Grayscale 이미지만 받는다.
=> 이미지를 처음에 색상이 있는 인자로 두고 받았더니 에러가 떴다.
Grayscale 만 가능한 것 같다.
- 두번째 : cv2.HOUGH_GRADIENT 만 지원
- 세번째 : 대부분 1을 지정하는데, 이 경우 입력 이미지와 동일한 해상도가 사용된다.
- 네번째 : 검출한 원의 중심과의 최소거리 값으로 이 최소값보다 작으면 원으로 판별이 안된다.
- param1 : Canny Edge 에 전달되는 인자값
- param2 : 검출 결과를 보며 적당히 조정해야 하는 값, 작으면 오류가 높고, 크면 검출률이 낮아짐
- minRadius : 원의 최소 반지름
- maxRadius : 원의 최대 반지
해당 함수를 활용하여 원을 검출하고, 그 원의 색상을 탐지해보는 코드는 아래와 같다.
1. 라이브러리 import
2. 색상 값과 딕셔너리 지정
3. 이미지 불러오기
cv2.IMREAD_COLOR 를 불러오면, 이미지에 값에는 color가 존재한다.
그러나 cv2.HoughCircles 함수에 color_image를 인자값으로 넣었다면, 에러가 뜬다.
그래서 image를 불러올 때, cv2.IMREAD_GRAYSCALE 의 인자로 그레이스케일 값으로 처리해준다.
4. cv2.HoughCircles 함수 사용하여 값을 받아온다.
int의 값으로 바꿔서 반내림 해줌
circle의 값은 x 좌표의 중심값, y좌표의 중심값, 그리고 반지름으로 구성된 원들의 다중 리스트가 나온다.
5. 사각형과 텍스트를 그려주는 코드를 작성해준다.
center 값에는 x, y 좌표의 중심값을 넣어주고,
radius 반지름의 값은 3번째 인자인 i[2]의 값을 넣어준다.
cv2.ractangle 의 함수로, 이미지에 검정색 사각형을 그려준다.
색상 color는 튜플로 선언해서, color_image의 x, y좌표의 중심값의 값을 넣어준다.
그리고 해당 값을 colorstr로 치환해서 색상을 표시해주도록 한다.
그리고 cv2.putText로 값을 그림에 그려주면 된다.
전체 코드는 아래와 같다.
import cv2
import numpy as np
black = (0, 0, 0)
colors_dict = { (255, 0, 0) : "Blue",
(0, 255, 0) : "Green",
(0, 0, 255): "Red" }
image_path = r"이미지 주소"
color_image = cv2.imread(image_path, cv2.IMREAD_COLOR)
image = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
circles = np.uint16(cv2.HoughCircles(image, cv2.HOUGH_GRADIENT, 1, image.shape[0] / 8, param1=100, param2=10, minRadius=0, maxRadius=100))
for i in circles[0]:
center = (i[0], i[1])
radius = i[2]
cv2.rectangle(color_image, (center[0]-radius , center[1]-radius, radius*2, radius*2) , black, 1)
color = tuple(color_image[center[1], center[0]])
colorstr = colors_dict[color]
cv2.putText(color_image, colorstr, (center[0]-radius , center[1]-radius) , 1 , 1, black)
cv2.imshow("ball", color_image)
cv2.waitKey(0)
결과값
3. 알고리즘을 활용하여 0, 0 부터 끝까지 진행하면서 원을 발견하면 그 원을 탐지
이 방식은 생각을 할 수는 있는데, 구현하는게 정말 어려운 방식이라서 아무도 못했을거라고 생각한 방식이다.
그러나 내 앞자리 코딩 천재 승혁님이 구상과 구현을 마무리하고 발표까지,,
그래서 코드를 받았다. (감사합니다.)
1. 앞의 선언은 비슷하다.
2. 나는 팀원이 그림판으로 그려줬는데, 그것까지 구현해주셨다.(존경..)
3. 색상을 찾아주는 함수
4. bfs로 진행
image를 픽셀로 생각해서, dxs, dys를 선언해주었고,
queue로 처음시작 x, y의 값을 넣어주고, 해당 index에 방문을 했다고 해준다.
queue를 돌면서, 해당 x, y 값을 변경시켜준다.
만약에 해당 index에 방문하지 않았고, 색상이 원하는 값과 같다면 queue에 nx, ny를 추가해서
색상 원이 그려지는 것이 끝날때까지 반복한다.
5. 해당 박스를 찾기
색상을 찾고, 해당 색상이 원하는 타겟 색상에 존재한다면,
추가로 index에 처음 방문하는 것이라면 bfs 함수 실행
그 return 값을 상하좌우 좌표값을 boxs 안에 넣어둔다.
6. 텍스트와 네모 표시
boxes 값에 저장해두었던 것을 꺼내와서
cv2.rectangle, cv2.putText 함수를 사용해서 그려준다.
전체 코드
import numpy as np
import cv2
color_dict = {
"blue": [255, 0, 0],
"green": [0, 255, 0],
"red": [0, 0, 255]
}
height, width = 640, 480
full_visited_indexs = [[False]*width for _ in range(height)]
image_path = r"이미지_경로"
image = cv2.imread(image_path, cv2.IMREAD_COLOR)
image = cv2.resize(image, (width, height))
def find_color(x, y):
pixel_value = image[y, x].tolist()
for key, value in color_dict.items():
if value == pixel_value:
return key
return None
dxs = [1, 0, -1, 0]
dys = [0, 1, 0, -1]
def bfs(start_x, start_y, target_color):
queue = [(start_x, start_y)]
full_visited_indexs[start_y][start_x] = True
visited_points = []
while queue:
x, y = queue.pop(0)
visited_points.append((x, y))
for dx, dy in zip(dxs, dys):
nx, ny = x + dx, y + dy
if 0 <= nx < width and 0 <= ny < height:
if not full_visited_indexs[ny][nx]:
if find_color(nx, ny) == target_color:
full_visited_indexs[ny][nx] = True
queue.append((nx, ny))
return visited_points
boxes = []
def draw_rec_text(boxes):
for box in boxes:
min_x, min_y, max_x, max_y, color = box
if color == "red":
cv2.rectangle(image, (min_x, min_y), (max_x, max_y), (0,0,255), 1)
cv2.putText(image, color, (min_x, min_y), cv2.FONT_HERSHEY_SIMPLEX, 1, (0,0,255), 1)
if color == "green":
cv2.rectangle(image, (min_x, min_y), (max_x, max_y), (0,255,0), 1)
cv2.putText(image, color, (min_x, min_y), cv2.FONT_HERSHEY_SIMPLEX, 1, (0,255,0), 1)
if color == "blue":
cv2.rectangle(image, (min_x, min_y), (max_x, max_y), (255,0,0), 1)
cv2.putText(image, color, (min_x, min_y), cv2.FONT_HERSHEY_SIMPLEX, 1, (255,0,0), 1)
def draw_box():
count = 0
for y in range(height):
for x in range(width):
color = find_color(x, y)
if color in ["red", "blue", "green"]:
if not full_visited_indexs[y][x]:
visited_indexs = bfs(x, y, color)
min_x = min(pt[0] for pt in visited_indexs)
max_x = max(pt[0] for pt in visited_indexs)
min_y = min(pt[1] for pt in visited_indexs)
max_y = max(pt[1] for pt in visited_indexs)
boxes.append((min_x, min_y, max_x, max_y, color))
count += 1
print("총 색영역 개수:", count)
if __name__ == "__main__":
draw_box()
draw_rec_text(boxes)
cv2.imshow("Draw Circle", image)
cv2.waitKey(0)
cv2.destroyAllWindows()
결과값은 아래와 같다.
하나의 문제를 해결하는데에 있어서
정말 다양하게 접근하여 문제를 풀어나갈 수 있다는 점이 재미있었다.
나 혼자 해결하고자 했으면 하나의 방법만 생각하고 넘어갔을텐데
여러 사람과 함께 동일한 문제를 풀어가려고 하니 확실히 다양한 관점으로 배울 수 있어서 좋았다
출처 및 참고
- OpenCV 공식 문서 : https://docs.opencv.org/4.x/da/d53/tutorial_py_houghcircles.html
- https://stackoverflow.com/questions/50568668/understanding-houghcircles-in-python-opencv-cv2
- https://docs.opencv.org/3.4/d4/d70/tutorial_hough_circle.html
- 승혁님의 코드
'공부' 카테고리의 다른 글
Large Language Models are Zero-Shot Reasoners 논문 리뷰 (3) | 2025.02.02 |
---|---|
Docker 개념 및 핵심 설명, 왜 핫한가? (2) | 2025.01.19 |
docker: error during connect: dockerDesktopLinuxEngine: The system cannot find the file specified. (0) | 2025.01.14 |
클라우드 서비스 Azure vs AWS 비교 (4) | 2025.01.12 |
[leetcode] 125. Valid Palindrome -python (1) | 2024.11.19 |