본문 바로가기

OpenCV

분할(segmentation) (2)

1. 허프 변환(hough transform)

■ 허프 변환은 영상/이미지에서 모양을 찾는 방법으로 이미지의 형태를 찾거나 깨진 영역을 복원하는 데 사용된다. 특히. 컴퓨터 비전에서 직선 검출은 주로 허프 변환 기법을 사용한다.

참고) https://opencv-python.readthedocs.io/en/latest/doc/25.imageHoughLineTransform/imageHoughLineTransform.html

x, y 좌표 공간(2차원)에서 극좌표계 형식으로 직선의 방정식을 변환하는 이유는 y축과 평행한 수직선의 기울기는 무한대가 되어야 하는데, 직선의 방정식으로는 y축과 평행한 수직선을 표현할 수 없기 때문이다. 

■ 극좌표계로 변환했을 때 로우는 원점에서 직선까지의 수직 거리, 세타는 원점에서 직선에 수직선을 내렸을 때 x 축과 이루는 각도이다.

참고)  https://m.blog.naver.com/windowsub0406/220894462409

 

[Udacity] SelfDrivingCar- 2-3. 차선 인식(hough transform)

Python3.5 그리고 Opencv 3.1 기준 코드 작성 셋팅만 제대로 ...

blog.naver.com

참고)  https://shpark98.github.io/devcourse-til/230411-1.-%ED%97%88%ED%94%84%EB%B3%80%ED%99%98-%EA%B8%B0%EB%B0%98-%EC%B0%A8%EC%84%A0%EC%9D%B8%EC%8B%9D/#hough-space

 

[2023-04-11] 1. 허프변환 기반 차선인식

허프변환 기반 차선인식

shpark98.github.io

1.1 허프 변환 직선 검출

■ 이미지는 수많은 픽셀로 구성된다. 이 픽셀 중에서 엣지를 찾아낸 다음, 엣지 픽셀들 중 직선 관계를 갖는 픽셀들만 선택하는 것이 허프 변환 직선 검출의 핵심이다. 

■ OpenCV에서는 허프 변환의 수학적 이론이 구현된 cv2.HoughLines(img, rho, theta, threshold, lines, srn=0, stn=0, min_theta, max_theta) 함수를 제공한다.

- img는 8비트, 1채널 바이너리 이미지, 캐니 엣지 선 적용

- rho는 로우 값의 범위(0~1 실수)로 거리 측정 해상도(더 정확히는 축적 배열에서 로우 값의 해상도)

- theta는 세타 값의 범위(0~18- 정수) (더 정확히는 축적 배열에서 세타 값의 해상도)

- threshold는 직선으로 판단할 임곗값. 즉, 만나는 점의 기준이다. 임곗값이 작으면 많은 선이 검출되지만 정확도가 떨어지고, 임곗값이 크면 검출되는 선의 개수가 감소하지만 정확도가 올라간다.

- srn은 멀티 스케일 허프 변환에서 로우 해상도를 나누는 값. 직선 검출에서는 사용하지 않으므로 0으로 지정

- stn은 멀티 스케일 허프 변환에서 세타 해상도를 나누는 값. 직선 검출에서는 사용하지 않으므로 0으로 지정

- min_theta와 max_theta는 검출할 직선의 최소, 최대 세타 값(=각도)

- rho와 theta 파라미터를 조정해 거리와 각도를 얼마나 세밀하게 게산할 것인지 정할 수 있다.

- 단, theta  구간을 촘촘하게 나누면 정밀한 직선 검출이 가능하지만 연산 시간이 늘어나고, 구간을 듬성하게 나누면 연산 속도는 빠르지만, 정확도가 낮아질 수 있다.

- 이는 rho와 theta 파라미터가 cv2.HoughLines() 함수 내부에서 사용할 축적 배열의 크기를 결정하는 역할을 하기 때문이다. 축적 배열에서 로우 간격은 1픽셀 단위, 세타는 1 라디안 단위로 처리한다.

img = cv2.imread('frame01.jpg')
img2 = img.copy()

imgray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 그레이 스케일
edges = cv2.Canny(gray,50,150,apertureSize=3) # 캐니 엣지로 먼저 경곗값 검출

## 허프 변환 직선 검출
lines = cv2.HoughLines(edges,1,np.pi/180,100)

for i in range(len(lines)): # 검출된 직선 순회
    for rho, theta in lines[i]: 
        a, b = np.cos(theta), np.sin(theta)
        x0, y0 = a*rho, b*rho # x, y 절편 좌표

        ## 직선 방정식을 그리기 위한 시작점과 끝점
        x1, y1 = int(x0 + 1000*(-b)), int(y0+1000*(a))
        x2, y2 = int(x0 - 1000*(-b)), int(y0 -1000*(a))
        cv2.line(img, (x1, y1), (x2, y2), (0,0,255), 1) # 빨간 직선 그리기

- 동일한 방법으로 threshold = 130으로 지정했을 때의 허프 변환 직선 검출을 수행해서 threshold = 100으로 지정했을 때의 결과와 비교하면 다음 그림과 같다.

plt.subplot(131),plt.imshow(img2[:,:,::-1]),plt.title('original'),plt.xticks([]); plt.yticks([])
plt.subplot(132),plt.imshow(img[:,:,::-1]),plt.title('threshold = 100'),plt.xticks([]); plt.yticks([])
plt.subplot(133),plt.imshow(img3[:,:,::-1]),plt.title('threshold = 130'),plt.xticks([]); plt.yticks([])

plt.show()

[출처] https://opencv-python.readthedocs.io/en/latest/doc/25.imageHoughLineTransform/imageHoughLineTransform.html

- 이 예에서 적합한 threshold 값은 100임을 확인할 수 있다.

- 이렇게 허프 변환은 적합한 파라미터 값을 찾기 위해 값을 바꿔가면서 적합한 값을 찾아야 한다.

 

1.2 확률적 허프 변환(probabilistic Hough transform) 직선 검출

■ OpenCV는 기본적인 허프 변환 직선 검출 방법 외에 확률적 허프 변환 함수 cv2.HoughLinesP(img, rho, theta, threshold, lines, minLineLength, maxLineGap)를 제공한다.
- minLineLength는 검출할 직선의 최소 길이
- maxLineGap는 직선으로 간주할 최대 간격
- 나머지 피라미터는 cv2.HoughLines()와 동일하다.

- 함수의 반환 값은 직선의 시작과 끝점의 좌표이므로,   cv2.HoughLines() 함수를 이용했을 때처럼 직선을 이미지에 그리기 위해 별도의 변환이 필요하지 않다.
기본적인 허프 변환 직선 검출 방법은 모든 점에 대해 선을 그어 가면서 직선을 찾기 때문에 연산량이 많다. 이를 개선한 방법이 확률적 허프 변환에 의한 직선 검출이다.
이 방법은 모든 점을 고려하지 않고 무작위로 선정한 픽셀에 대해 허프 변환을 수행한다. 
무작위로 직선을 검출하기 때문에 cv2.HoughLines( ) 함수를 사용했을 때보다 검출되는 직선이 적다. 그러므로 캐니 엣지 검출을 먼저 적용할 때 엣지를 더 뚜렷하게, cv2.HoughLinesP 함수의 threshold 값은 낮게 지정해야 한다.

img = cv2.imread('hough_images.jpg')
edges = cv2.Canny(img,50,200,apertureSize = 3)
gray = cv2.cvtColor(edges,cv2.COLOR_GRAY2BGR)
minLineLength,  maxLineGap = 100, 0

## 확률적 허프 변환 직선 검출
lines = cv2.HoughLinesP(edges, 1, np.pi/360, 160, minLineLength, maxLineGap)

for i in range(len(lines)): # 검출된 직선 순회
    for x1,y1,x2,y2 in lines[i]:
        cv2.line(img,(x1,y1),(x2,y2),(0,0,255),3) # 빨간 직선 그리기
cv2.imshow('img1',img)
cv2.waitKey(0)
cv2.destroyAllWindows()

- 왼쪽은 minLineLength,  maxLineGap = 100, 10, 오른쪽은 비교를 위해 minLineLength,  maxLineGap = 100, 10으로 동일한 직선 검출을 수행한 결과이다.

- 우선 왼쪽의 경우 화단 부분의 윤곽선(엣지)이 복잡해서 해당 부분도 직선으로 검출되었다. 

- 직선으로 간주할 엣지 픽셀의 최대 간격을 늘린 결과, 오른쪽 그림처럼 화단 부분의 복잡하고 매우 작은 엣지는 무시되었지만, 건물 부분의 엣지도 무시된 것을 볼 수 있다. 

 

1.3 허프 변환 원 검출

■ 허프 변환을 이용한 원 검출도 직선 검출처럼 직선의 방정식을 파라미터 공간으로 변환하는 허프 변환을 중심 좌표가 (a, b)이고 반지름이 r인 원의 방정식 \( (x-a)^2 + (y-b)^2 = r^2 \)에 적용하면 된다.

■ 하지만 원의 방정식은 세 개의 파라미터를 가지므로 허프 변환을 적용하면 3차원 파라미터 공간에서 축적 배열 계산을 수행해야 하기 때문에 많은 메모리와 연산 시간이 필요하다. 

■ OpenCV에서는 위와 같은 일반적인 허프 변환 대신 허프 그래디언트 방법(Hough gradient method)을 사용하는 cv2.HoughCircles(img, method, dp, minDist, circles, param1, param2, minRadius, maxRadius) 함수를 제공한다. 

 cv2.HoughCircles( ) 함수는 캐니 엣지를 수행한 뒤, 소벨 필터를 적용해 엣지의 그래디언트를 누적하는 방법으로 원을 검출한다.

cv2.HoughCircles( ) 함수의 파라미터는 다음과 같다.

- img에는 입력 영상/이미지로 1채널 그레이스케일 영상/이미지를 지정한다.

- method는 검출 방식으로 현재 cv2.HOUGH_GRADIENT만 사용할 수 있다.

- dp는 입력 영상과 경사 누적의 해상도 반비례율로 1에 가까울수록 정확하게 원을 검출하며, 값이 커질수록 부정확해진다. 즉, dp 값은 값을 점차 키워가면서 적합한 값을 찾아야 한다.

- dp 값을 1로 지정할 경우 입력 영상/이미지와 같은 크기의 축적 배열을 사용한다. 2로 지정하면 입력 영상/이미지의 가로와 세로 크기를 2로 나눈 크기의 축적 배열을 사용한다.

- minDist는 인접한 원들 중심 간의 최소 거리이다. 두 원의 중심점 사이의 거리가 minDist보다 작으면 두 원 중 하나는 검출되지 않는다. 그리고 동심원을 검출할 때 이 값이 0이면 최소 거리가 0이므로 동심원 검출을 수행할 수 없다.

- param1은 캐니 엣지에 전달할 스레시홀드 최댓값이다. 

- param2는 경사도 누적 경곗값으로 값이 작을수록 잘못된 원을 검출할 수 있다.

- minRadius, maxRadius는 검출할 원의 최소, 최대 반지름이다. 만약, 검출할 원의 대략적인 크기를 알고 있어  minRadius와 maxRadius 값을 적절하게 지정한다면 연산 속도를 향상시킬 수 있다.

img = cv2.imread('coins_spread.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (3,3), 0) # 가우시안 블러링으로 노이즈 제거

## 허프 변환 원 검출
circles = cv2.HoughCircles(blur, cv2.HOUGH_GRADIENT, dp = 1.5, minDist = 30, circles =  None, param1 = 200)

- cv2.HoughCircles( ) 함수 내에서 자체적으로 캐니 엣지를 사용하므로, 노이즈만 제거하고 별도로 엣지 검출은 수행하지 않는다.

if circles is not None:
    circles = np.uint16(np.around(circles))
    for i in circles[0,:]:
        cv2.circle(img,(i[0], i[1]), i[2], (0, 255, 0), 2) # 원 둘레에 초록색 원 그리기
        cv2.circle(img, (i[0], i[1]), 2, (0,0,255), 5) # 원 중심점에 빨강색 원 그리기

cv2.imshow('hough circle', img)
cv2.waitKey(0)
cv2.destroyAllWindows()

 

2. 연속 영역 분할

2.1 거리 변환(Distatnce Transformation)

■ 이미지에서 객체 영역을 정확히 파악하려면 객체 영역의 뼈대(skeleton)를 찾는 것이 중요하다. 

■ 거리 변환은 그 뼈대를 검출하는 방법으로 외곽 경계로부터 가장 멀리 떨어진 객체 영역의 뼈대를 찾는다. 정확히는 바이너리 이미지를 이용해 픽셀 값이 0인 배경으로부터 가장 멀리 떨어진 전경(객체 영역)의 픽셀 값 255인 부분을 찾는다.

■ OpenCV에서는 거리 변환을 수행하는 cv2.distanceTransform(src, distanceType, maskSize) 함수를 제공한다.

- src는 입력 영상/이미지로 1채널 그레이스케일 영상/이미지를 지정한다.

- distanceType에는 거리를 계산할 방식을 지정한다.

distance Type 의미
cv2.DIST_L1 맨해튼 거리(Manhattan distance)
cv2.DIST_L2 유클리드 거리(Euclidean distance)
cv2.DIST_L12 L1+L2 형태로 결합된 거리 계산을 수행
cv2.DIST_C 체비쇼프 거리(Chebyshev distance)
cv2.DIST_FAIR 공정 거리(Fair distance)
cv2.DIST_WELSCH 웰치 거리(Welsch distance)
cv2.DIST_HUBER 후버 거리(Huber distance)

- maskSize는 거리 변환 커널(필터)의 크기이다.

img = cv2.imread('VthxV.png')
img.shape
```#결과#```
(486, 729, 3)
````````````

gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 그레이스케일
_, th = cv2.threshold(gray, 40, 255, cv2.THRESH_BINARY) # 바이너리
## 거리 변환 
dst = cv2.distanceTransform(th, cv2.DIST_L1, 3)

## 거리 값을 0 ~ 255 범위로 정규화
dst = cv2.normalize(dst, None, alpha=0, beta=255, norm_type=cv2.NORM_MINMAX, dtype=cv2.CV_8U)

## 거리 값에서 완전한 뼈대(픽셀 값 255) 추출
skeleton = cv2.adaptiveThreshold(dst, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 7, -3)

- 첫 번째 이미지는 거리 변환을 하기 위해 입력으로 사용한 바이너리 이미지,

- 두 번째 이미지는 0~255 범위로 정규화한 거리 변환 결과 이미지이다. 외곽 경계로부터 멀어질수록 흰색이 짙어지는 것을 볼 수 있다.

- 마지막 이미지는 경계로부터 가장 멀리 떨어져 있는 뼈대의 픽셀(흰색, 255)을 추출한 것이다.

 

2.2 연결 요소 레이블링

■ 레이블링은 바이너리 이미지에서 연결된 요소를 분리하는 방법으로 다음 그림과 같이 바이너리 이미지 전체에서 픽셀 값이 0으로 끊어지지 않는 부분끼리 동일한 값을 부여해서 분리한다.

[출처] https://charlezz.com/?p=45334

■ OpenCV에서는 cv2.connectedComponents(src, labels, connectivity=8, ltype=CV_32S) 함수를 이용해 레이블링을 수행할 수 있다.

- src는 바이너리 스케일의 영상/이미지

- connectivity는 연결성을 검사할 방향 개수로 8 또는 4를 지정할 수 있다.

- ltype은 결과 레이블의 dtype으로 CV_325 또는 CV_16S를 지정할 수 있다.

- 함수의 결과로 레이블 맵 행렬과 레이블 개수가 반환된다.

cv2.connectedComponents( ) 함수는 기본적인 레이블링 함수로 레이블 맵을 반환하지만, 객체 영역의 통계 정보(레이블링 수행 후, 각각의 객체 영역이 어느 위치에 있는지, 어느 정도 크기인지 등)가 제공되지 않는다. 

cv2.connectedComponentsWithStats(src, labels, stats, centroids, connectivity, ltype) 함수를 이용하면 레이블 맵과 레이블링된 객체 영역의 통계 정보를 한꺼번에 얻을 수 있다.

- 함수의 결과로 레이블 맵 행렬과 레이블 개수뿐만 아니라 [x 좌표, y 좌표, 폭, 높이, 너비]에 대한 정보가 담긴 N(레이블 개수) x 5 행렬과 각 레이블의 중심점 좌표를 추가로 반환한다.

img = cv2.imread('chessboard.jpg')
img2 = np.zeros_like(img) # 결과 이미지 
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 그레이스케일
_, th = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY) # 바이너리

## 바이너리 이미지에 레이블링
retval, labels, stats, centroids = cv2.connectedComponentsWithStats(th)
print(retval)
```#결과#```
34
````````````

- 총 34개의 레이블링이 적용된 것을 확인할 수 있다.

## 레이블이 같은 영역에 랜덤한 색상 적용

for i in range(retval): ## 레이블 갯수 만큼 반복
    img2[labels==i] =  [int(j) for j in np.random.randint(0,255, 3)] 
    # 0부터 255 사이 3개의 값 랜덤으로 뽑음 # 이 3개의 값이 순서대로 B, G, R에 해당된다.
    # 이 B,G,R을 img2[labels==i]에 넣는다.
    
plt.subplot(121),plt.imshow(img[:,:,::-1]),plt.title('origin'),plt.xticks([]); plt.yticks([])
plt.subplot(122),plt.imshow(img2[:,:,::-1]),plt.title('labeled'),plt.xticks([]); plt.yticks([])
plt.show()

- 연결된 부분(같은 레이블을 가지는 영역)끼리는 동일한 색상이 칠해진 것을 볼 수 있다.

 

2.3 워터셰드(Watershed)

참고) https://opencv-python.readthedocs.io/en/latest/doc/27.imageWaterShed/imageWaterShed.html 

■ 워터셰드 알고리즘의 개념은 다음과 같다.
- 이미지를 그레이스케일로 변환하면 각 픽셀의 값은 0~255 사이의 값을 갖게 된다. 이 값으로 픽셀 값의 높고 낮음을 구분할 수 있다.
- 이것을 지형의 높낮이로 가정하면, 높은 픽셀 값은 봉우리(peak), 낮은 부분을 골짜기(valley)라고 표현할 수 있다. 
- 이곳에 물을 붓는다고 하면 골짜기 영역 중 같은 높이의 연속된 영역에는 물웅덩이가 형성되고, 높낮이가 다르므로 물웅덩이를 경계로 만들면 물이 서로 섞이지 않을 것이다.
- 이런 경계선을 이미지의 구분 지점으로 설정하여 다음 그림처럼 이미지를 분할하는 것이 워터셰드 알고리즘의 개념이다.

[출처] http://cmm.ensmp.fr/~beucher/wtshed.html

OpenCV에서는 워터셰드 함수 cv2.watershed(img, markers)를 제공한다.

- img는 입력 이미지

- markers는 마커로 입력 이미지와 크기가 같은 1차원 배열이다.

- markers의 값은 경계를 찾고자 하는 픽셀 영역은 -1을 갖게 하고, 나머지 연속된 영역에서는 동일한 정숫값을 갖게 한다. 

- 워터셰드 함수를 적용했을 때 생기는 -1인 영역이 경곗값이 된다.

img = cv2.imread('watershed.jpg')
img2 = img.copy()

gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) # 그레이 스케일
ret, thresh = cv2.threshold(gray,0,255,cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU) # 바이너리

#Morphology의 opening, closing을 통해서 노이즈나 Hole제거
kernel = np.ones((3,3),np.uint8)
opening = cv2.morphologyEx(thresh,cv2.MORPH_OPEN,kernel,iterations=2)

- marker를 통해 전경(객체)과 배경, 그리고 객체가 아닌 아무것도 확신하지 못하는 지역에 레이블을 붙여야 한다. 

- 먼저 전경과 배경을 구분해야 한다.

- dilate를 이용해서 서로 연결되지 않은 부분을 확실한 배경으로 간주한다.

- 거리 변환을 적용하면 뼈대(Skeleton) 이미지를 얻을 수 있다. 이 뼈대 이미지에 스레시홀딩을 적용해서 확실한 전경을 찾는다.

sure_bg = cv2.dilate(opening,kernel,iterations=3) # dilate
dist_transform = cv2.distanceTransform(opening,cv2.DIST_L2,5) # 거리 변환
ret, sure_fg = cv2.threshold(dist_transform,0.5*dist_transform.max(),255,0) # 스레시홀딩
sure_fg = np.uint8(sure_fg)

- 전경과 배경을 구분했다면, 이제 확실하지 않은 영역을 파악해야 한다. 이 영역은 배경에서 전경을 뺀 영역이다.

## Background에서 Foregrand를 제외한 영역을 Unknow영역으로 파악
unknown = cv2.subtract(sure_bg, sure_fg)

- 전경, 배경, 그리고 unknown 영역을 찾았으면, 이제 전경에 레이블링 작업을 한다. 

- 레이블링으로 각 영역에 라벨을 붙이게 되고, 이것이 바로 marker가 된다.

## 전경에 레이블링작업
ret, markers = cv2.connectedComponents(sure_fg)
markers = markers + 1
markers[unknown == 255] = 0

- 이제 cv2.watershed( ) 함수를 적용하면 객체의 경계는 -1 값을 갖게 된다.

## watershed를 적용 
markers = cv2.watershed(img,markers)
img[markers == -1] = [0,255,0] # 마커에 -1로 표시된 경계를 초록색으로 표시
plt.subplot(231),plt.imshow(thresh, cmap = 'gray'),plt.title('binary'),plt.axis('off')
plt.subplot(232),plt.imshow(sure_bg, cmap = 'gray'),plt.title('sure_bg'),plt.axis('off')
plt.subplot(233),plt.imshow(dist_transform, cmap = 'gray'),plt.title('dist_transform'),plt.axis('off')
plt.subplot(234),plt.imshow(sure_fg, cmap = 'gray'),plt.title('sure_fg'),plt.axis('off')
plt.subplot(235),plt.imshow(unknown, cmap = 'gray'),plt.title('unknown'),plt.axis('off')
plt.subplot(236),plt.imshow(markers),plt.title('markers'),plt.axis('off')

plt.show()

cv2.imshow('original', img2)
cv2.imshow('result', img)

cv2.waitKey()
cv2.destroyAllWindows()

 

2.4 그랩컷(Graph Cut)

참고) https://deep-learning-study.tistory.com/240

■ 그랩컷은 픽셀을 그래프의 정점(vertex)으로 간주하고 픽셀들을 전경 영역과 배경 영역으로 분할하는 최적의 컷(cut)을 찾는 방법이다.

■ 전경과 배경을 분리하기 위해(최적 컷을 찾기 위해) 색상 분포를 추정해서 동일한 레이블을 가진 정점(픽셀)끼리 분리한다.

■ OpenCV에서는 그랩컷 함수 cv2.grabCut(img, mask, rect, bgdModel, fgdModel, iterCount, mode)를 제공한다.

- img는 입력 이미지

- mask는 입력 이미지와 크기가 같은 1채널 배열로 배경과 전경을 구분하는 값을 저장한다.

- mask는 4개의 값으로 구성된다.

mask 의미
cv2.GC_BGD(0) 확실한 배경
cv2.GC_FGD(1) 확실한 전경
cv2.GC_PR_BGD(2) 아마도 배경
cv2.GC_PR_FGD(3) 아마도 전경

-  rect는 ROI 영역으로 전경이 있을 것으로 추측되는 영역의 사각형 좌표(x1, y1, x2, y2)이다.

- bgdModel는 임시 배경 모델 행렬,  fgdModel는 임시 전경 모델 행렬

- iterCount는 그랩컷을 수행할 반복 횟수

- mode는 동작 방법으로 cv2.GC_INIT_WITH_RECT을 지정하면 rect에 지정한 좌표를 기준으로 그랩컷을 수행하고, cv2.GC_INIT_WITH_MASK를 지정하면 mask에 지정한 값을 기준으로 그랩컷을 수행한다. 

- 함수의 결과로 mask, bgdModel, fgdModel이 반환된다.

cv2.grabCut( )의 mode에 cv2.GC_INIT_WITH_RECT을 지정하면 세 번째 파라미터 rect에 지정한 사각형 좌표로 전경과 배경을 분리한다. 

- 분리된 결과는 두 번째 파라미터인 mask에 할당. 이 mask가 함수의 결과로 반환된다.

- mask에 할당받은 값이 0이면 확실한 배경, 1이면 확실한 전경, 2와 3이면 아마도 배경, 전경일 가능성이 있다는 의미이다.

- 이렇게 1차적으로 전경과 배경을 분리한 뒤, mode에 cv2.GC_INIT_WITH_MASK를 지정해서 다시 cv2.grabCut( )를 적용하면, 더 정확한 mask를 얻을 수 있다. 이때 bgdModel, fgdModel은 cv2.grabCut( ) 함수 내부에서 사용할 임시 배경과 전경이다. 

- bgdModel, fgdModel을 통해 재귀적으로 전경과 배경을 계속 분리해서 mask를 업데이트한다. 단, bgdModel, fgdModel은 이전 연산을 반영하기 때문에 같은 영상/이미지 처리 시에는 그 내용을 변경하면 안 된다.

img = cv2.imread('img3.jpg')

x,y,w,h = cv2.selectROI('img', img, False)
if w and h:
    roi = img[y:y+h, x:x+w]
    cv2.imshow('cropped', roi)  
    cv2.moveWindow('cropped', 0, 0) 
    print(x,y,w,h)
    
cv2.imshow('img', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
```#결과#```
67 97 179 67
````````````
rect = [67, 97, 179, 67] # 전경으로 사용할 영역
mask = np.zeros(img.shape[:2], dtype=np.uint8)  # (검정색으로 채워진) 마스크 생성

## 그랩컷 적용
cv2.grabCut(img, mask, tuple(rect), None, None, 1, mode = cv2.GC_INIT_WITH_RECT)
img2 = img.copy()

## 마스크로 확실한 배경과 아마도 배경으로 표시된 영역을 0(검은색)으로 채우기
img2[(mask==cv2.GC_BGD) | (mask==cv2.GC_PR_BGD)] = 0

cv2.imshow('grabcut', img2)
cv2.waitKey()
cv2.destroyAllWindows()

'OpenCV' 카테고리의 다른 글

매칭(Matching) (2)  (2) 2024.12.24
매칭(Matching) (1)  (0) 2024.12.23
분할(segmentation) (1)  (0) 2024.12.22
모폴로지(Morphology) 연산, 이미지 피라미드(Image Pyramid)  (0) 2024.12.21
필터와 블러링  (0) 2024.12.18