导读
本文主要介绍基于基于距离变换 分水岭算法实现粘连物体的分割与计数方法,并对比Halcon与OpenCV实现差异。
背景介绍
在实际的视觉应用场景中,我们常常会遇到物体/元件的计数问题,而计数时比较常见的情形就是物体相邻或粘连,对相邻或粘连物体的分割将直接影响着最终计数的准确性。后面将分篇介绍粘连物体分割计数的常用方法,包括:
【1】形态学 连通域处理方法
【2】距离变换 分水岭分割方法
【3】其他方法
本文将对第【2】种方法分别用Halcon和OpenCV实现并做简单对比。
实例演示与实现步骤
* 实例一:糖豆分割与计数
测试图像(图片来源--Halcon例程图):
实现步骤:
【1】阈值处理:区间固定阈值或OTSU阈值
【2】距离变换
【3】分水岭算法
【4】求区域重叠部分(交集)
【5】结果标记与显示
Halcon实现代码:
代码语言:javascript复制dev_clear_window()
dev_get_window (WindowHandle)
*读取图片
read_image (Image, 'pellets')
get_image_size (Image, Width, Height)
*自动阈值分割
rgb1_to_gray (Image, GrayImage)
threshold (GrayImage, Regions, 110, 255)
connection (Regions, ConnectedRegions)
select_shape (ConnectedRegions, SelectedRegions, 'area', 'and', 40, 50000)
*欧式距离函数的距离变换
distance_transform (SelectedRegions, DistanceImage, 'octagonal', 'true', Width, Height)
*int4转byte
convert_image_type (DistanceImage, ImageConverted, 'byte')
*图像取反
invert_image (ImageConverted, ImageInvert)
*图像比例增强 按最大比例增强对比度
scale_image_max (ImageInvert, ImageScaleMax)
*分水岭算法
watersheds_threshold (ImageScaleMax, Basins, 30)
select_shape (Basins, SelectedBasins, 'area', 'and', 2000, 50000)
gen_contour_region_xld (SelectedBasins, Contours, 'border')
*取出两个区域中重叠的部分
intersection (SelectedBasins, SelectedRegions, RegionIntersection)
dev_display(Image)
dev_display(RegionIntersection)
dev_display(Image)
dev_set_draw ('margin')
dev_set_line_width (3)
dev_display (RegionIntersection)
dev_set_color ('red')
dev_set_line_width (2)
area_center (RegionIntersection, Area, Row, Column)
gen_cross_contour_xld (Cross, Row, Column, 15, 0.785398)
count_obj (RegionIntersection, Number)
*设置字体颜色
dev_set_color ('green')
*设置文字大小
set_display_font (WindowHandle, 30, 'mono', 'true', 'false')
*设置文字位置
set_tposition (WindowHandle, 15, 220)
write_string(WindowHandle, 'count=' Number)
OpenCV实现代码与效果:
代码语言:javascript复制#公众号:OpenCV与AI深度学习
#作者:Color Space
import cv2
import numpy as np
import random as rd
def watershed_algorithm(image):
src = image.copy()
# 边缘保留滤波EPF 去噪
blur = cv2.pyrMeanShiftFiltering(image,sp=21,sr=55)
cv2.imshow("blur", blur)
# 转成灰度图像
gray = cv2.cvtColor(blur, cv2.COLOR_BGR2GRAY)
# 得到二值图像区间阈值
ret, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY cv2.THRESH_OTSU)
cv2.imshow('thres image', binary)
# 距离变换
dist = cv2.distanceTransform(binary, cv2.DIST_L2, 3)
dist_out = cv2.normalize(dist, 0, 1.0, cv2.NORM_MINMAX)
cv2.imshow('distance-Transform', dist_out * 100)
ret, surface = cv2.threshold(dist_out, 0.5*dist_out.max(), 255, cv2.THRESH_BINARY)
cv2.imshow('surface', surface)
sure_fg = np.uint8(surface)# 转成8位整型
cv2.imshow('Sure foreground', sure_fg)
# Marker labelling
ret, markers = cv2.connectedComponents(sure_fg) # 连通区域
print(ret)
markers = markers 1 #整个图 1,使背景不是0而是1值
# 未知区域标记(不能确定是前景还是背景)
kernel = np.ones((3, 3), np.uint8)
binary = cv2.morphologyEx(binary, cv2.MORPH_DILATE, kernel, iterations=1)
unknown = binary - sure_fg
cv2.imshow('unknown',unknown)
# 未知区域标记为0
markers[unknown == 255] = 0
# 区域标记结果
markers_show = np.uint8(markers)
cv2.imshow('markers',markers_show*100)
# 分水岭算法分割
markers = cv2.watershed(image, markers=markers)
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(markers)
markers_8u = np.uint8(markers)
#print(max_val)
colors = [(255,0,0), (0,255,0), (0,0,255), (255,255,0),
(255,0,255), (0,255,255), (255,128,0), (255,0,128),
(128,255,0), (128,0,255), (255,128,128), (128,255,255)]
for i in range(2,int(max_val 1)):
ret, thres1 = cv2.threshold(markers_8u, i-1, 255, cv2.THRESH_BINARY)
ret2, thres2 = cv2.threshold(markers_8u, i, 255, cv2.THRESH_BINARY)
mask = thres1 - thres2
cv2.imshow('mask',mask)
#color = (rd.randint(0,255), rd.randint(0,255), rd.randint(0,255))
#image[markers == i] = [rd.randint(0,255), rd.randint(0,255), rd.randint(0,255)]
#image[markers == i] = [colors[i-2]]
contours,hierarchy = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
cv2.drawContours(image,contours,-1,colors[(i-2)],-1)
#cv2.drawContours(src,contours,-1,colors[(i-2)],-1)
M = cv2.moments(contours[0])
cx = int(M['m10']/M['m00'])
cy = int(M['m01']/M['m00'])#轮廓重心
cv2.drawMarker(image, (cx,cy),(0,0,255),1,10,2)
cv2.drawMarker(src, (cx,cy),(0,0,255),1,10,2)
cv2.putText(src,"count=%d"%(int(max_val-1)),(220,30),0,1,(0,255,0),2)
cv2.putText(image,"count=%d"%(int(max_val-1)),(220,30),0,1,(0,255,0),2)
cv2.imshow('regions', image)
result = cv2.addWeighted(src,0.6,image,0.5,0) #图像权重叠加
cv2.imshow('result', result)
src = cv2.imread('./pellets.png')
cv2.imshow('src', src)
watershed_algorithm(src)
cv2.waitKey(0)
cv2.destroyAllWindows()
OpenCV标记结果:
* 实例二:硬币分割与计数
测试图像(图片来源--网络):
实现步骤与实例一相同,代码中只需要对部分参数微调即可,大家可以自己尝试,实现结果如下:
Halcon实现结果:
OpenCV实现结果:
对比与总结
【1】适用情形:距离变换 分水岭算法适用于一些粘连并不严重或粘连区域远比物体本身小的情况(粘连严重或重叠部分过多则不适用);
【2】Halcon中距离变换 分水岭算法使用相对简单,效果更稳定。
OpenCV中的距离变换特别要注意二值化图像中物体部分中间不要有孔洞,如果有需要填充后再做距离变换,否则会影响距离变换的效果。分水岭算法需要自己做预处理,设置markers告诉分水岭算法哪里是前景物体,哪里是未知区域,哪里是背景,使用相对麻烦,有时候还可能出现难以分割的情况,比如下图:
Halcon分割结果:
OpenCV分割结果(分割失败):
这种情况下,就直接换形态学 连通域方法处理即可:
【3】OpenCV实现代码里面用到了cv2.pyrMeanShiftFiltering,这里是使用均值漂移图像分割算法做滤波操作,抹除物体内部的纹理,方便阈值处理和距离变换时得到更好的结果,以硬币分割为例:
【4】实际应用中没有万能的方法,具体情况具体分析,选择合适的方法使用。