1。 分水嶺分割方法

它是依賴於形態學的,影象的灰度等級不一樣,如果影象的灰度等級一樣的情況下怎麼人為的把它造成不一樣?可以透過距離變換實現,這樣它們的灰度值就有了階梯狀的變換。風水嶺演算法常見的有三種方法:(1)基於浸泡理論的分水嶺分割方法;(2)基於連通圖方法;(3)基於距離變換的方法。OpenCV 中是基於距離變換的分割方法,就相當於我們的小山頭(認為造成的)。

基本的步驟:

影象分割實戰-分水嶺分割方法和GrabCut 演算法

例子1 粘連物件分離和計數。

例子程式碼:

#include

#include

using

namespace

std

using

namespace

cv

void

test

()

{

Mat

srcImg

srcImg

=

imread

“pill_002。png”

);

if

srcImg

empty

())

{

cout

<<

“could not load image。。。

\n

<<

endl

}

namedWindow

“Original image”

CV_WINDOW_AUTOSIZE

);

imshow

“Original image”

srcImg

);

Mat

grayImg

binaryImg

shiftedImg

//做濾波,使影象更加平滑,保留邊緣,類似於雙邊濾波

pyrMeanShiftFiltering

srcImg

shiftedImg

21

51

);

namedWindow

“shifted”

CV_WINDOW_AUTOSIZE

);

imshow

“shifted”

shiftedImg

);

cvtColor

shiftedImg

grayImg

COLOR_BGR2GRAY

);

//轉為灰度影象

//二值化

threshold

grayImg

binaryImg

0

255

THRESH_BINARY

|

THRESH_OTSU

);

namedWindow

“binary”

CV_WINDOW_AUTOSIZE

);

imshow

“binary”

binaryImg

);

//距離變換

Mat

distImg

distanceTransform

binaryImg

distImg

DistanceTypes

::

DIST_L2

3

CV_32F

);

//歸一化,因為距離變換後得出來的值都比較小。

normalize

distImg

distImg

0

1

NORM_MINMAX

);

namedWindow

“distance”

CV_WINDOW_AUTOSIZE

);

imshow

“distance”

distImg

);

//這個二值化的作用是尋找區域性最大。

threshold

distImg

distImg

0。4

1

THRESH_BINARY

);

namedWindow

“distance_binary”

CV_WINDOW_AUTOSIZE

);

imshow

“distance_binary”

distImg

);

//生成 marker

Mat

distMaskImg

// distImg 得到的是 0- 1之間的數,轉化成8位單通道的。

distImg

convertTo

distMaskImg

CV_8U

);

vector

<

vector

<

Point

>>

contours

//找到 marker 的輪廓

findContours

distMaskImg

contours

RETR_EXTERNAL

CHAIN_APPROX_SIMPLE

Point

0

0

));

//create marker 填充 marker

Mat

markersImg

=

Mat

::

zeros

srcImg

size

(),

CV_32SC1

);

for

int

i

=

0

i

<

contours

size

();

i

++

{

drawContours

markersImg

contours

static_cast

<

int

>

i

),

Scalar

::

all

static_cast

<

int

>

i

+

1

),

-

1

);

}

circle

markersImg

Point

5

5

),

3

Scalar

255

),

-

1

);

//形態學操作 - 彩色影象,目的是去掉干擾,讓結果更好。

Mat

kernel

=

getStructuringElement

MORPH_RECT

Size

3

3

),

Point

-

1

-

1

));

morphologyEx

srcImg

srcImg

MORPH_ERODE

kernel

);

//完成分水嶺變換

watershed

srcImg

markersImg

);

Mat

mark

=

Mat

::

zeros

markersImg

size

(),

CV_8UC1

);

markersImg

convertTo

mark

CV_8UC1

);

bitwise_not

mark

mark

Mat

());

namedWindow

“watershed”

CV_WINDOW_AUTOSIZE

);

imshow

“watershed”

mark

);

//下面的步驟可以不做,最好做出來讓結果顯示更美觀。

//生成隨機顏色

vector

<

Vec3b

>

colors

for

int

i

=

0

i

<

contours

size

();

i

++

{

int

r

=

theRNG

()。

uniform

0

255

);

int

g

=

theRNG

()。

uniform

0

255

);

int

b

=

theRNG

()。

uniform

0

255

);

colors

push_back

Vec3b

((

uchar

b

uchar

g

uchar

r

));

}

//顏色填充和最終顯示

Mat

dstImg

=

Mat

::

zeros

markersImg

size

(),

CV_8UC3

);

int

index

=

0

for

int

i

=

0

i

<

markersImg

rows

i

++

{

for

int

j

=

0

j

<

markersImg

cols

j

++

{

index

=

markersImg

at

<

int

>

i

j

);

if

index

>

0

&&

index

<=

contours

size

())

{

dstImg

at

<

Vec3b

>

i

j

=

colors

index

-

1

];

}

else

{

dstImg

at

<

Vec3b

>

i

j

=

Vec3b

0

0

0

);

}

}

}

cout

<<

“number of objects:”

<<

contours

size

()

<<

endl

namedWindow

“Final Result”

CV_WINDOW_AUTOSIZE

);

imshow

“Final Result”

dstImg

);

}

int

main

()

{

test

();

waitKey

0

);

return

0

}

效果:

影象分割實戰-分水嶺分割方法和GrabCut 演算法

總結:有時候會導致碎片化,過度分割,因為二值化中如果有很多小的黑點或碎片,在分割的時候導致很多 mask ,即小山頭太多了,這個時候我們要考慮怎麼去合併它,可以透過聯通區域的直方圖,或者畫素值均值相似程度等。

例子2:影象分割

#include

#include

using

namespace

std

using

namespace

cv

//執行分水嶺演算法函式

Mat

watershedCluster

Mat

&

srcImg

int

&

numSegments

);

//結果顯示函式

void

DisplaySegments

Mat

&

markersImg

int

numSegments

);

void

test

()

{

Mat

srcImg

srcImg

=

imread

“toux。jpg”

);

if

srcImg

empty

())

{

cout

<<

“could not load image。。。

\n

<<

endl

}

namedWindow

“Original image”

CV_WINDOW_AUTOSIZE

);

imshow

“Original image”

srcImg

);

int

numSegments

Mat

markers

=

watershedCluster

srcImg

numSegments

);

DisplaySegments

markers

numSegments

);

}

Mat

watershedCluster

Mat

&

srcImg

int

&

numSegments

{

//二值化

Mat

grayImg

binaryImg

cvtColor

srcImg

grayImg

COLOR_BGR2GRAY

);

threshold

grayImg

binaryImg

0

255

THRESH_BINARY

|

THRESH_OTSU

);

//形態學和距離變換

Mat

kernel

=

getStructuringElement

MORPH_RECT

Size

3

3

),

Point

-

1

-

1

));

morphologyEx

binaryImg

binaryImg

MORPH_OPEN

kernel

Point

-

1

-

1

));

Mat

distImg

distanceTransform

binaryImg

distImg

DistanceTypes

::

DIST_L2

3

CV_32F

);

normalize

distImg

distImg

0。0

1。0

NORM_MINMAX

);

//開始生成標記

threshold

distImg

distImg

0。1

1。0

THRESH_BINARY

);

normalize

distImg

distImg

0

255

NORM_MINMAX

);

distImg

convertTo

distImg

CV_8UC1

);

//CV_32F 轉成 CV_8UC1

//標記開始

vector

<

vector

<

Point

>>

contours

vector

<

Vec4i

>

hireachy

findContours

distImg

contours

hireachy

RETR_CCOMP

CHAIN_APPROX_SIMPLE

);

if

contours

empty

())

{

return

Mat

();

}

Mat

markersImg

distImg

size

(),

CV_32S

);

markersImg

=

Scalar

::

all

0

);

for

int

i

=

0

i

<

contours

size

();

i

++

{

drawContours

markersImg

contours

i

Scalar

i

+

1

),

-

1

8

hireachy

INT_MAX

);

}

circle

markersImg

Point

5

5

3

Scalar

255

),

-

1

);

//分水嶺變換

watershed

srcImg

markersImg

);

numSegments

=

contours

size

();

return

markersImg

}

void

DisplaySegments

Mat

&

markersImg

int

numSegments

{

//生成隨機顏色

vector

<

Vec3b

>

colors

for

int

i

=

0

i

<

numSegments

i

++

{

int

r

=

theRNG

()。

uniform

0

255

);

int

g

=

theRNG

()。

uniform

0

255

);

int

b

=

theRNG

()。

uniform

0

255

);

colors

push_back

Vec3b

((

uchar

b

uchar

g

uchar

r

));

}

//顏色填充和最終顯示

Mat

dstImg

=

Mat

::

zeros

markersImg

size

(),

CV_8UC3

);

int

index

=

0

for

int

i

=

0

i

<

markersImg

rows

i

++

{

for

int

j

=

0

j

<

markersImg

cols

j

++

{

index

=

markersImg

at

<

int

>

i

j

);

if

index

>

0

&&

index

<=

numSegments

{

dstImg

at

<

Vec3b

>

i

j

=

colors

index

-

1

];

}

else

{

dstImg

at

<

Vec3b

>

i

j

=

Vec3b

255

255

255

);

}

}

}

cout

<<

“number of objects:”

<<

numSegments

<<

endl

namedWindow

“Final Result”

CV_WINDOW_AUTOSIZE

);

imshow

“Final Result”

dstImg

);

}

int

main

()

{

test

();

waitKey

0

);

return

0

}

效果圖:

影象分割實戰-分水嶺分割方法和GrabCut 演算法

2。 GrabCut 演算法分割影象

GrabCut 演算法的原理前面有介紹過,這裡就不在介紹了,具體可以看下文章末尾往期推薦中閱讀。下面例子實現影象中物件的摳圖。

基本步驟:

影象分割實戰-分水嶺分割方法和GrabCut 演算法

例子程式碼:

#include

#include

using

namespace

std

using

namespace

cv

int

numRun

=

0

//演算法迭代次數

bool

init

=

false

Rect

rect

Mat

srcImg

MaskImg

bgModel

fgModel

//滑鼠回撥函式

void

onMouse

int

event

int

x

int

y

int

flags

void

*

param

);

void

showImg

();

//顯示畫的圖片

void

setRoiMask

();

//選擇 ROI 的函式

void

runGrabCut

();

//執行演算法函式

static

void

ShowHelpText

();

//提示使用者操作函式

void

test

()

{

srcImg

=

imread

“toux。jpg”

);

if

srcImg

empty

())

{

cout

<<

“could not load image。。。

\n

<<

endl

}

namedWindow

“Original image”

CV_WINDOW_AUTOSIZE

);

imshow

“Original image”

srcImg

);

//初始化 mask,單通道 8 位

MaskImg

create

srcImg

size

(),

CV_8UC1

);

//在不知道它是前景還是背景的情況下,把它全部設為背景。

MaskImg

setTo

Scalar

::

all

GC_BGD

));

//結果不是 0 就是 1 GC_BGD為0

setMouseCallback

“Original image”

onMouse

0

);

while

true

{

char

c

=

char

waitKey

0

);

if

c

==

‘n’

// 按下 n 建開始執行演算法

{

runGrabCut

();

numRun

++

showImg

();

cout

<<

“current iteative times:”

<<

numRun

<<

endl

}

if

c

==

27

{

break

}

}

}

void

onMouse

int

event

int

x

int

y

int

flags

void

*

param

{

switch

event

{

case

EVENT_LBUTTONDOWN

rect

x

=

x

rect

y

=

y

rect

width

=

1

rect

height

=

1

break

case

EVENT_MOUSEMOVE

if

flags

&

EVENT_FLAG_LBUTTON

{

rect

=

Rect

Point

rect

x

rect

y

),

Point

x

y

));

showImg

();

}

break

case

EVENT_LBUTTONUP

if

rect

width

>

1

&&

rect

height

>

1

{

showImg

();

}

break

default

break

}

}

void

showImg

()

{

Mat

result

binMask

binMask

create

MaskImg

size

(),

CV_8UC1

);

binMask

=

MaskImg

&

1

if

init

{

srcImg

copyTo

result

binMask

);

}

else

{

srcImg

copyTo

result

);

}

rectangle

result

rect

Scalar

0

0

255

),

2

8

);

namedWindow

“Original image”

CV_WINDOW_AUTOSIZE

);

imshow

“Original image”

result

);

}

void

setRoiMask

()

{

//GC_BGD = 0 明確屬於背景的畫素

//GC_FGD = 1 明確屬於前景的畫素

//GC_PR_BGD = 2 可能屬於背景的畫素

//GC_PR_FGD = 3 可能屬於前景的畫素

MaskImg

setTo

GC_BGD

);

//為了避免選擇越界

rect

x

=

max

0

rect

x

);

rect

y

=

max

0

rect

y

);

rect

width

=

min

rect

width

srcImg

cols

-

rect

x

);

rect

height

=

min

rect

height

srcImg

rows

-

rect

y

);

//把我們選取的那一塊設為前景

MaskImg

rect

)。

setTo

Scalar

GC_PR_FGD

));

}

void

runGrabCut

()

{

if

rect

width

<

2

||

rect

height

<

2

{

return

}

if

init

{

grabCut

srcImg

MaskImg

rect

bgModel

fgModel

1

);

}

else

{

grabCut

srcImg

MaskImg

rect

bgModel

fgModel

1

GC_INIT_WITH_RECT

);

init

=

true

}

}

static

void

ShowHelpText

()

{

cout

<<

“請先用滑鼠在圖片視窗中標記出屬於前景的區域”

<<

endl

cout

<<

“然後再按按鍵【n】啟動演算法”

<<

endl

cout

<<

“按鍵【ESC】- 退出程式”

<<

endl

}

int

main

()

{

ShowHelpText

();

test

();

waitKey

0

);

return

0

}

效果圖:

影象分割實戰-分水嶺分割方法和GrabCut 演算法

影象分割實戰-分水嶺分割方法和GrabCut 演算法

歡迎關注我的微信公眾號“

OpenCV影象處理演算法

”,主要是記錄自己學習影象處理演算法的歷程,包括特徵提取、目標跟蹤、定位、機器學習和深度學習,每一個例子都會提供原始碼和例子所用的資料,歡迎同行的同學關注我和我一起虛度光陰吧!!!