yellow1 = np.array([16, 80,140]) #노랑색 최솟값
yellow2 = np.array([90, 255,255]) #노랑색 최댓값
while (True):
ret, src = cap.read() #영상파일 읽어드리기
hsv = cv2.cvtColor(src, cv2.COLOR_BGR2HSV)
mask_yellow = cv2.inRange(hsv, yellow1, yellow2) # 노랑최소최대값을 이용해서 maskyellow값지정
res_yellow = cv2.bitwise_and(src, src, mask=mask_yellow) # 노랑색만 추출하기
srcs = res_yellow #imgs에 추출한 노랑색 저장
imgray = cv2.cvtColor(srcs, cv2.COLOR_BGR2GRAY)
=> 앞에 포스팅이랑 동일하여 생략 [openCV] Trackbar HSV범위를 이용한 Contour
<Canny Edge>
Gradient 를 이용한 edge 검출방법을 개선한 방법
https://deep-learning-study.tistory.com/206
[파이썬 OpenCV] 영상의 그래디언트와 에지 검출하기 -cv2.magnitude, cv2.phase
황선규 박사님의 과 패스트 캠퍼스 OpenCV 강의를 공부하면서 내용을 정리해 보았습니다. 예제 코드 출처 : 황선규 박사님 github홈페이지 『OpenCV 4로 배우는 컴퓨터 비전과 머신 러닝』 예제 소스
deep-learning-study.tistory.com
(Canny Edge를 따기 전에 가우시안 필터링을 해주는 것이 좋다)
Canny Edge의 Gradient는 소벨 마스크를 사용(크기와 방향을 모두 고려)니

방향은 4구역으로만 판단을 한다.
45도를 한 구역으로 설정하고 180도에 대한 4구역을 구합니다.
나머지 180도는 계산된 구역의 대칭부분이므로 4구역만 이용(직선 이니깐)
Non maximum suppression을 사용
(최대 크기의 픽셀만 골라내서 에지 픽셀로 설정하는 것)
=> 하나의 에지가 여러 개의 픽셀로 표현되는 현상을 없애기 위하여


이런 식으로 최대값만 뽑아낸다고 한다(자세한건 필요할 때 더 공부하자...)

Hysteresis edge tracking(히스테리시스 에지 트래킹)
Canny Edge는 임계값을 두개를 사용함
조명이나 이런 것들의 영향을 최소화 하기 위해서
상한 임계값(Max val), 하한 임계값(min val)를 사용
1. Max val < x
이건 edge라고 판단 (strong edge)
2. min val < x < Max val
(weak edge)
Strong edge와 연결 되어 있는 경우에만 edge라 판다
3. x < min val
edge가 아님
<min val을 점점 낮추는 상황> 점점 더 많이 연결이 된다
(무시당하는 애들이 줄어 들면서 strong edge와 연결되는 애들이 늘어나는 상황 연출)

인터넷을 검색하다가 이 방법에 제일 좋은 것 같다
1. 먼저 threshold1와 threshold2를 같은 값으로 한다.
2. 검출되길 바라는 부분에 엣지가 표시되는지 확인하면서 threshold2 값을 조정한다.
3. 2번의 조정이 끝나면, threshold1를 사용하여 엣지를 연결시킨다.
dst = cv2.Canny(imgray, 50, 200, None, 3) #canny처리하기
canny 처리하기(결과 이미지)
cdst = cv.cvtColor(dst, cv.COLOR_GRAY2BGR)
흑백선 (추출한 선들만)을 컬처로 바꿔줌 (빨강이 더 보기 편하여서)
cdstP = np.copy(cdst) #위에 컬러로 변환한 영상 저장
확률적 허프변환과 비교를 하려고 따로 빼 두었다
허프변환 (Hough TransForm)
굉장히 어려워 보이니 위키백과 차근차근 읽어보자
어떠한 점을 지나는 직선은 xcosθ + ysinθ = r 으로 표현할 수 있다.
지금 이 파트에서의 관심에 대해 주의깊게 생각해야 한다.
Hough는 이 문제를 아래와 같이 생각했다.
[그림 1]과 같이 어떤 점을 지나가는 무수히 많은 직선들이 존재한다.
그러면 xcosθ + ysinθ = r 에서 x,y를 상수로 잡고 theta와 r을 변수로 잡는다면???
(상수인 이유 : 점 (x,y)는 무조건 지나야 하기 때문에)
이렇게 되면 (x,y)를 지나는 무수히 많은 직선을 theta와 r로 표현 할 수 있다.
자 여기에서
sin@ = x/sqrt(x^2 + y^2) , cos@ = y/sqrt((x^2 + y^2))를 만족하는 @가 있다고 가정을 하면
위의 xcosθ + ysinθ = r 는
sin(θ + @) = r /sqrt((x^2 + y^2)) 로 바꿀수 있다 (θ + @ = x1 , r /sqrt((x^2 + y^2) = y1로 치환하면)
=> y1 = k * sin(x1)
즉 , (r과 θ를 축으로 하는 극좌표계에서) sin곡선으로 표현 할 수 있는 것이다
아래의 3개의 점이 주어졌다고 가정을 하자
우리가 목표하는 선은 핑크색 선이고
첫번째, 두번째, 세번째, 점에 대해 각각
θ을 1 ~ 180까지 변화를 하면서 원점에서 (x,y)까지의 거리(r)을 구한다.(위의 예시는 30씩 증가)
해당 좌표( r, θ )를 가지고 xy평면에 직선을 그리면 다음과 같은 sin곡선을 그릴수 있고
세 곡선의 교점 핑크색 점의 값이 우리가 찾는 선의 극좌표 이다. ( r, θ )
이를 확장 하여서 n개의 점에 대해 그려보면 n개의 점을 지나는 직선을 유사하게 그릴 수 있는 것이다.
(n개를 모두 지나는 직선은 그리 많지는 않기 때문에 threshold를 n보다 작은 숫자로 하는 것이 국룰)
lines = cv2.HoughLines(img, rho, theta, threshold, lines, srn=0, stn=0, min_theta, max_theta)
img: 입력 이미지, 1 채널 바이너리 스케일
rho , theta거리와 각도를 얼마나 세밀하게 할 것인지
rho: 거리 측정 해상도, 0~1
theta: 각도, 라디안 단위 (np.pi/0~180)
threshold: 직선으로 판단할 최소한의 동일 개수 (작은 값: 정확도 감소, 검출 개수 증가 / 큰 값: 정확도 증가, 검출 개수 감소)
lines: 검출 결과, N x 1 x 2 배열 (batch, ? , (r, θ) )
srn, stn: 멀티 스케일 허프 변환에 사용, 선 검출에서는 사용 안 함
min_theta, max_theta: 검출을 위해 사용할 최대, 최소 각도
점진성 확률적 허프변환 (Progressive Probabilistic Hough TransForm)
앞에 일반적인 허프변환은 모든 점을 탐색하므로 시간 소요가 많이 된다 (최적화 필요)
=> 모든 점이 아닌 임의의 점 일부만 계산
lines = HoughLinesP(검출 영상, 거리, 각도, 임계값, 최소 선의 길이, 최대 선의 간격)
임계값까지는 hough 변환과 동일한 parameter지만 뒤에 두개가 다르다
최소 선의 길이 : 검출된 직선이 가져야하는 최소한의 선 길이
최대 선 간격 : 검출된 직선들 사이의 최대 허용 간격
return 값 lines 는 (N, 1, 4) 차원의 형태를 가진다
마지막 차원인 lines[i][0][0], lines[i][0][1], lines[i][0][2], lines[i][0][3] 은 각각 x1, y1, x2, y2
즉, 시작점 (lines[i][0][0], lines[i][0][1]) 끝점 (lines[i][0][2], lines[i][0][3])이다
lines = cv.HoughLines(dst, 1, np.pi / 180, 150, None, 0, 0)
허프변환 적용
if lines is not None:
for i in range(0, len(lines)):
len()문자열의 길이구하기-허프변환으로 검출된 선의 개수 만큼
rho = lines[i][0][0]
극좌표(r, θ) 중에 길이 r 를 가져옴
theta = lines[i][0][1]
극좌표(r, θ) 중에 각도 θ를 가져옴
a = math.cos(theta)
b = math.sin(theta)
x0 = a * rho
y0 = b * rho
x0 = rcosθ , y0 = rsinθ
pt1 = (int(x0 + 1000 * (-b)), int(y0 + 1000* (a))) #시작점
pt2 = (int(x0 - 1000 * (-b)), int(y0 - 1000 * (a))) #종료점
#그냥 이미지 밖으로 보내고 싶어서 1000을 곱했지만 좀더 젠틀한 방법은 아래의 코드를 이용
scale = src.shape[0] + src.shape[1]
x1 = int(x0 + scale * -b)
y1 = int(y0 + scale * a)
x2 = int(x0 - scale * -b)
y2 = int(y0 - scale * a)
cv.line(cdst, pt1, pt2, (0, 0, 255), 3, cv.LINE_AA)
#라인그리기
#점진성 확률적 허프변환
linesP = cv.HoughLinesP(dst, 1, np.pi / 180, 50, None, 50, 10)
if linesP is not None:
for i in range(0, len(linesP)):
l = linesP[i][0]
밑에 깔끔하게 적고 싶어서
cv.line(cdstP, (l[0], l[1]), (l[2], l[3]), (0, 0, 255), 3, cv.LINE_AA)
시작점 : ( I[0] , I[1] ) 끝점 : ( I[2] , I[3] ) 이고 (0,0,255) -> 빨강색으로 그려라
if (l[2]-l[0]) == 0:
continue
else:
grad = (l[3] - l[1])/(l[2]-l[0])
grad_theta = (math.atan(grad))
회전각(로봇이 방향이 틀어짐을 해결하기 위해 중심기준 틀어진 각도 θ를 구한다)
if 0 <grad_theta < 1:
print("Warning :: 좌회전 필요")
elif -1 < grad_theta < 0:
print("Warning :: 우회전 필요")
else:
print("회전 각도 양호")
sleep(0.3)
cv.imshow("Original", src) #원본파일
cv.imshow("yellow_det", hsv) #노랑감지
cv.imshow("Detected Lines (in red) - Standard Hough Line Transform", res_yellow) #허프변환라인
cv.imshow("Detected Lines (in red) - Probabilistic Line Transform", cdstP) #확률적허프변환라인
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
<추가 설명>
cv2.line
cv2.line(img, pt1, pt2, color, thickness = 1, lineType = cv.Line_8, shift = 0)
img : 이미지 파일
pt1 : 시작점의 좌표 (x1,y1)
pt2 : 종료점의 좌표 (x2,y2)
color : 색상 (B,G,R) : (0~255, 0~255, 0~255)
lineType
** Bresenham's algorithm **
- Line_8 : 8-connected line -> 오른쪽, 왼쪽, 위쪽, 아래쪽, 대각선 영역 고려(선분에 픽셀을 할당할 때)
- Line_4 : 4-connected line -> 오른쪽, 왼쪽, 위쪽, 아래쪽 영역만 고려 (선분에 픽셀을 할당할 때)
** Anti-Aliasing ** : 영상신호의 결함을 없애기 위해 가장자리 부분에서 발생하는 계단현상 없애고 부드럽게 보이게 함
가우스 필터링을 사용하여서 넓은 선의 경우 항상 끝이 동그래진다
- Line_AA : antialiased line (나라면 이거 쓸 듯)
이게 필요한 이유 직선의 방정식으로 점들을 연결하면 실수형태의 점이 발생하게 되지만
이미지는 사각격자 구조로 이루어져있어 모두 정수값으로 구성되기에 필요함
shift : fractional bit -> 이걸 사용하면 소숫점 이하의 값이 포함된 실숫값 좌표도 그릴 수 있다
(sub pixel(서비 픽셀)정렬을 이용해서 소숫점 이하 자릿수 표현이 가능)
<완성코드>
import cv2
import math
import cv2 as cv
import numpy as np
from time import sleep
def detect_angle(cap):
yellow1 = np.array([16, 80,140]) #노랑색 최솟값
yellow2 = np.array([90, 255,255]) #노랑색 최댓값
while (True):
ret, src = cap.read() #영상파일 읽어드리기
hsv = cv2.cvtColor(src, cv2.COLOR_BGR2HSV)
mask_yellow = cv2.inRange(hsv, yellow1, yellow2) # 노랑최소최대값을 이용해서 maskyellow값지정
res_yellow = cv2.bitwise_and(src, src, mask=mask_yellow) # 노랑색만 추출하기
srcs = res_yellow #imgs에 추출한 노랑색 저장
imgray = cv2.cvtColor(srcs, cv2.COLOR_BGR2GRAY)
dst = cv2.Canny(imgray, 50, 200, None, 3) #canny처리하기
cdst = cv.cvtColor(dst, cv.COLOR_GRAY2BGR) #흑백 ---->컬러 선을 빨갛게 보이기 위
cdstP = np.copy(cdst) #위에 컬러로 변환한 영상 저장
lines = cv.HoughLines(dst, 1, np.pi / 180, 150, None, 0, 0) #허프변환
if lines is not None:
for i in range(0, len(lines)): #len()문자열의 길이구하기-허프변환으로 검출된 선의 개수 만큼
rho = lines[i][0][0]
theta = lines[i][0][1]
a = math.cos(theta)
b = math.sin(theta)
x0 = a * rho #이걸로 교차점에서 턴하는 값 지정가능
y0 = b * rho #이걸로 좌우 정할수 있는데 비슷한 맥락으로 나는 tan를 씀
pt1 = (int(x0 + 1000 * (-b)), int(y0 + 1000* (a))) #시작점
pt2 = (int(x0 - 1000 * (-b)), int(y0 - 1000 * (a))) #종료점
cv.line(cdst, pt1, pt2, (0, 0, 255), 3, cv.LINE_AA) #라인그리기
#확률적 허프변환
linesP = cv.HoughLinesP(dst, 1, np.pi / 180, 50, None, 50, 10)
if linesP is not None:
for i in range(0, len(linesP)):
l = linesP[i][0]
cv.line(cdstP, (l[0], l[1]), (l[2], l[3]), (0, 0, 255), 3, cv.LINE_AA)
if (l[2]-l[0]) == 0:
continue
else:
grad = (l[3] - l[1])/(l[2]-l[0])
grad_theta = (math.atan(grad))
print(grad_theta)
if 0 <grad_theta < 1:
print("Warning :: 좌회전 필요")
elif -1 < grad_theta < 0:
print("Warning :: 우회전 필요")
else:
print("회전 각도 양호")
sleep(0.3)
cv.imshow("Original", src) #원본파일
cv.imshow("yellow_det", hsv) #노랑감지
cv.imshow("Detected Lines (in red) - Standard Hough Line Transform", res_yellow) #허프변환라인
cv.imshow("Detected Lines (in red) - Probabilistic Line Transform", cdstP) #확률적허프변환라인
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
#HSV H(Hue; 색조), S(Saturation; 채도), V(Value; 명도),powerpoint에서 찾은값에 H는 1/2해줘야함
#H:0~179 S:0~255 V:0~255
#cv2.Canny(가져올파일,임계값1,임계값2,커널크기,L2그라디언트)
#임계값1이하에 포함된 가장자리는 가장자리에서 제외
#임계값2이상에 포함된 가장자리는 가장가지로 간주
#커널크기 : Aperture size
#L2그라디언트 :L2방식 √((dI/dx)^2+(dI/dx)^2)의 사용 유무 없으면
#L1 ∥dI/dx∥+∥dI/dy∥사용 간주
#cv2.line(img,pt1,pt2,color,thickness,linetype,shift)
#img이미지 파일 pt1시작점 좌표(x,y) pt2종료점 좌표(x,y)
#color(blue,green,red) thickness(선두께 default 1)
#lineType(선 종류 default cv.Line_8)
#Line_8 : 8 connected line , Line_4 : 4 connected line , Line_AA antialiased line
#shift fractional bit (default 0)
#허프변환
#cv2.HoughLines(image, rho, theta, threshold[, lines[, srn[, stn[, min_theta[, max_theta]]]]])
#image - Output of the edge detector,회색조 이미지여야
#rho - r값의 범위 (0~1 실수) 주로 1 사용 , 매개변수의 해상도
#theta -θ값의 범위 (0~180 정수) pi/180=1
#threshold - 만나는 점의 기준, 숫자가 작으면 많은 선이 검출되지만 정확도가 떨어짐
#srn 및 stn 기본 매개 변수는 0
#확률적허프변환
#linesP = cv.HoughLinesP (dst, 1, np.pi / 180, 50, None , 50, 10)
#dst- edge 변환의 출력 (회색이여야함)
#lines: A vector that will store the parameters (xstart,ystart,xend,yend) of the detected lines
#rho : The resolution of the parameter r in pixels. We use 1 pixel.
#theta: The resolution of the parameter θ in radians. We use 1 degree (CV_PI/180)
#threshold: 선을 감지하기위한 최소 교차 수
#minLinLength: 선을 형성 할 수있는 최소 포인트 수. 이 포인트 수보다 적은 라인은 무시됩니다..
#maxLineGap: 같은 선에서 고려할 두 점 사이의 최대 간격.
참고
https://engineer-mole.tistory.com/243
[python/OpenCV] cv2.Canny():Canny방법을 이용하여 물체의 외곽선(엣지) 추출하기
외곽선(엣지)란 물체간 혹은 배경과의 경계를 일컫는 것으로, 외곽선(엣지) 검출이란 일반적으로 이미지 안의 화소치의 변화, 휘도의 변화가 커다란 부분을 검출하여 엣지를 추출하는 이미지 처
engineer-mole.tistory.com
https://deep-learning-study.tistory.com/207
[파이썬 OpenCV] 영상의 윤곽선 검출하기 - 캐니 에지 검출 - cv2.Canny
deep-learning-study.tistory.com
https://076923.github.io/posts/Python-opencv-28/
Python OpenCV 강좌 : 제 28강 - 직선 검출
직선 검출(Line Detection)
076923.github.io
https://076923.github.io/posts/Python-opencv-18/
Python OpenCV 강좌 : 제 18강 - 도형 그리기
도형 그리기(Drawing)
076923.github.io
'AI Track > CV' 카테고리의 다른 글
[Obj Det] Fast, Faster R-CNN, SPPNet (2 Stage Detectors) (0) | 2022.11.21 |
---|---|
[Obj Det] Basic of Object Detection(정의, mAP 등등) (0) | 2022.11.17 |
[openCV] Trackbar HSV범위를 이용한 Contour (0) | 2022.11.11 |
[openCV] linetracer 중앙정렬 (1) | 2022.11.09 |
[openCV] cv2.imshow 창이 안뜨는 문제 해결 (0) | 2022.11.09 |