Grab算法部分:
import cv2 as cv
import numpy as np
# 鼠标反馈 得到前景矩形起始点,终止点,宽高
def on_mouse(event, x, y, flag, param):
global rect
global leftButtonDown
global leftButtonUp
if event == cv.EVENT_LBUTTONDOWN:
rect[0] = x
rect[2] = x
rect[1] = y
rect[3] = y
leftButtonDown = True
leftButtonUp = False
if event == cv.EVENT_MOUSEMOVE:
if leftButtonDown and not leftButtonUp:
rect[2] = x
rect[3] = y
if event == cv.EVENT_LBUTTONUP:
if leftButtonDown and not leftButtonUp:
x_min = min(rect[0], rect[2])
y_min = min(rect[1], rect[3])
x_max = max(rect[0], rect[2])
y_max = max(rect[1], rect[3])
rect[0] = x_min
rect[1] = y_min
rect[2] = x_max
rect[3] = y_max
leftButtonUp = True
leftButtonDown = False
img = cv.imread('screenshot.png.jpg')
mask = np.zeros(img.shape[:2], np.uint8)
bgdModel = np.zeros((1, 65), np.float64)
fgdModel = np.zeros((1, 65), np.float64)
rect = [0, 0, 0, 0]
leftButtonUp = True
leftButtonDown = False
cv.namedWindow("img")
cv.setMouseCallback("img", on_mouse)
cv.imshow("img", img)
# 实时画出前景矩形,左键放开即开始分离前景
while cv.waitKey(2) == -1:
if leftButtonDown and not leftButtonUp:
img_copy = img.copy()
cv.rectangle(img_copy, (rect[0], rect[1]),
(rect[0] + rect[2], rect[1] + rect[3]), (0, 255, 0), 2)
cv.imshow("img", img_copy)
if not leftButtonDown and leftButtonUp and rect[0] != 0 and rect[1] != 0:
rect_copy = tuple(rect.copy())
print(rect_copy)
rect = [0, 0, 0, 0]
cv.grabCut(img, mask, rect_copy, bgdModel, fgdModel, 5, cv.GC_INIT_WITH_RECT)
# 背景设为0,与原图相乘,则背景为黑色
mask_two = np.where((mask == 2) | (mask == 0), 0, 1).astype('uint8')
img_show = img * mask_two[:, :, np.newaxis]
cv.imshow("grabcut", img_show)
cv.waitKey()
cv.destroyAllWindows()
分水岭算法:
主要对比了一下案例和网络上另一种用法:
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
src = cv.imread("coins.png")
img = src.copy()
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
ret, thresh = cv.threshold(gray, 127, 255, cv.THRESH_BINARY_INV + cv.THRESH_OTSU)
# 开运算
kernel = np.ones((3, 3), np.uint8)
opening = cv.morphologyEx(thresh, cv.MORPH_OPEN, kernel, iterations=2)
# 第二次膨胀
sure_bg = cv.dilate(opening, kernel, iterations=3)
# 距离变换
dist_transform = cv.distanceTransform(opening, cv.DIST_L2, 5)
_, sure_fg = cv.threshold(dist_transform, 0.7 * dist_transform.max(), 255, 0)
sure_fg = np.uint8(sure_fg)
# 边界未知区域
unknown = cv.subtract(sure_bg, sure_fg)
# 标记(会把背景标为0,所以整体加1)
_, markers = cv.connectedComponents(sure_fg)
markers = markers + 1
# 未知区域为0
markers[unknown == 255] = 0
plt.subplot(243), plt.imshow(markers)
# 分水岭
markers = cv.watershed(img, markers)
watershed = cv.convertScaleAbs(markers)
plt.subplot(244), plt.imshow(watershed)
plt.subplot(241), plt.imshow(img)
# 生成随机颜色
colorTab = np.zeros((np.max(markers) + 1, 3))
# 生成0~255之间的随机数
for i in range(len(colorTab)):
aa = np.random.uniform(0, 255)
bb = np.random.uniform(0, 255)
cc = np.random.uniform(0, 255)
colorTab[i] = np.array([aa, bb, cc], np.uint8)
bgrImage = np.zeros(img.shape, np.uint8)
# 遍历marks每一个元素值,对每一个区域进行颜色填充
for i in range(markers.shape[0]):
for j in range(markers.shape[1]):
# index值一样的像素表示在一个区域
index = markers[i][j]
# 判断是不是区域与区域之间的分界,如果是边界(-1),则使用白色显示
if index == -1:
img[i][j] = np.array([255, 255, 255])
else:
img[i][j] = colorTab[index]
plt.subplot(242), plt.imshow(img)
#################################
# canny边缘检测 函数返回一副二值图,其中包含检测出的边缘。
canny = cv.Canny(gray, 80, 150)
# 寻找图像轮廓 返回修改后的图像 图像的轮廓 以及它们的层次
contours, hierarchy = cv.findContours(canny, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
# 32位有符号整数类型,
marks = np.zeros(img.shape[:2], np.int32)
# 轮廓颜色
compCount = 0
index = 0
# 绘制每一个轮廓
for index in range(len(contours)):
# 对marks进行标记,对不同区域的轮廓使用不同的亮度绘制,相当于设置注水点,有多少个轮廓,就有多少个轮廓
# 图像上不同线条的灰度值是不同的,底部略暗,越往上灰度越高
marks = cv.drawContours(marks, contours, index, (index, index, index), 1, 8, hierarchy)
marks_copy = cv.convertScaleAbs(marks.copy())
plt.subplot(247), plt.imshow(marks_copy)
# 使用分水岭算法
marks = cv.watershed(src, marks)
afterWatershed = cv.convertScaleAbs(marks)
plt.subplot(248), plt.imshow(afterWatershed)
# 生成随机颜色
colorTab = np.zeros((np.max(marks) + 1, 3))
# 生成0~255之间的随机数
for i in range(len(colorTab)):
aa = np.random.uniform(0, 255)
bb = np.random.uniform(0, 255)
cc = np.random.uniform(0, 255)
colorTab[i] = np.array([aa, bb, cc], np.uint8)
bgrImage = np.zeros(img.shape, np.uint8)
# 遍历marks每一个元素值,对每一个区域进行颜色填充
for i in range(marks.shape[0]):
for j in range(marks.shape[1]):
# index值一样的像素表示在一个区域
index = marks[i][j]
# 判断是不是区域与区域之间的分界,如果是边界(-1),则使用白色显示
if index == -1:
bgrImage[i][j] = np.array([255, 255, 255])
else:
bgrImage[i][j] = colorTab[index]
plt.subplot(246), plt.imshow(bgrImage)
plt.show()
cv.waitKey(0)
cv.destroyAllWindows()
第二列是最终成图,第三列是预处理的markers,第四列是分水岭算法处理后的markers
第一种处理方法的步骤:二值化 => 开运算 => 膨胀 => 距离转换 => 找出位置区域 => 标记区域
第一种处理方法的步骤:canny => 标记轮廓
我们会发现两种思路是完全不同的,第一种重点是圆心的‘水’冲入‘山谷’中,第二种则相反
那么我们会怀疑,第二种既然是边缘‘水’泛滥到圆心,为何还会存在边缘分界呢?
其实是Canny算法将边缘大部分用两条曲线来描述,两股‘水’冲在一起形成分界,但也可看出,Canny效果不好的时候(即只有一条描述边缘),就会在局部形成大面积的泛滥,是极为不理想的效果。(最后一张图下方蓝色区域)
真正运用时,个人认为第一种明显更好,真正做到将每个物体分隔开,而第二种,会发现,如果出现物体粘连,并不能很好地分隔物体(其实没有用好分水岭)。
再放两个实例:(很明显看出两者的区别)