这里写Ostu阈值可以说是对之前的图像二值化的补充,Ostu阈值也可叫做大津法图像灰度自适应的阈值分割算法,它是由日本学者大津提出,并由他的名字命名的。话不多说,我们来写该怎么调用它。
在opencv 中实现Ostu算法使用的是cv.threshold函数(之前早已经写过),指定使用cv.THRESH_OTSU即可。(就这么跟你们说,个人感觉这个函数用来对图像分割是好用的一批,当然并不绝对)
这里我们回顾一下之前阈值分割的方法:
cv2.THRESH_BINARY 超过阈值部分取maxval(最大值),否则取0
cv2.THRESH_BINARY_INV THRESH_BINARY的反转
cv2.THRESH_TRUNC 大于阈值部分设为阈值,否则不变
cv2.THRESH_TOZERO 大于阈值部分不改变,否则设为0
cv2.THRESH_TOZERO_INV THRESH_TOZERO的反转
CV_THRESH_OTSU需要与上面5中结合使用,可以写成:THRESH_BINARY | CV_THRESH_OTSU
import cv2
import numpy as np
import matplotlib.pyplot as plt#这里引入matplotlib,它的使用方法,可自行百度,方法都有
img=cv2.imread('lena.png')
img_gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret, thresh1 = cv2.threshold(img_gray, 127, 255, cv2.THRESH_BINARY)
ret, thresh2 = cv2.threshold(img_gray, 127, 255, cv2.THRESH_BINARY|cv2.THRESH_OTSU)
img=cv2.cvtColor(img,cv2.COLOR_BGR2RGB)
#这里将img转为rgb,这样用matplotlib显示原图是就会是原来的颜色了
titles = ['Original Image', 'BINARY', 'Ostu']
images = [img, thresh1, thresh2 ]
plt.figure(figsize=(10, 5))#设置figure窗口的大小
for i in range(3):
plt.subplot(1, 3, i + 1), plt.imshow(images[i], 'gray')
plt.title(titles[i])
plt.xticks([]), plt.yticks([])
plt.show()
可能我这里用的图像不是很好,所以小伙伴看不出它的有点,但是个人感觉这个方法在图像分割中挺好用的。
下面我们将获得图形的中心,然后并标注,程序是修改了之前的识别轮廓的那一节(这里可以说是对之前那一节的补充)
图像的矩可以帮助我们计算图像的中心,面积等。
函数cv2.moments()会将计算得到的矩以一个字典的形式返回。
import cv2
import numpy as np
img = cv2.imread('shapes.png')
img1=img.copy()
imgGray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)#转为灰度图
imgBlur = cv2.GaussianBlur(imgGray,(5,5),1)#高斯降噪
imgCanny = cv2.Canny(imgBlur,50,50)#canny边缘检测
contours, hierarchy = cv2.findContours(imgCanny, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE) # 提取图像外轮廓,并返回至contours
for cnt in contours:#遍历所有的轮廓
peri = cv2.arcLength(cnt, True)
approx = cv2.approxPolyDP(cnt, 0.02 * peri, True)
x, y, w, h = cv2.boundingRect(approx) # 获得每个轮廓的外接矩形
if len(approx) == 3:#三个点的认为是三角形
Type = "Triangle"
elif len(approx) == 4:#四个点可能为矩形or正方形,所以这里引入了轮廓的外接矩形的长宽比来判断
Ratio = w / float(h)
if Ratio > 0.97 and Ratio < 1.03:
Type = "Square"
else:
Type = "Rectangle"
elif len(approx) > 4:#大于四个点的,在该图中认为是圆形
Type = "Circles"
else:
Type = "None"
cv2.putText(img1, Type, (x + (w // 2) - 10, y + (h // 2) - 10), cv2.FONT_HERSHEY_COMPLEX, 0.7, (0, 0, 0),1) # 这里选择黑色,显示更清楚(原谅色看不清)
M=cv2.moments(cnt)
cx = int(M['m10'] / M['m00'])
cy = int(M['m01'] / M['m00'])
cv2.circle(img, (cx, cy), 3, (0, 0, 255), -1)
cv2.drawContours(img, cnt, -1, (0, 255, 0), 2)#原谅色,哈哈
cv2.imshow("img",img)
cv2.imshow("img_output", img1)
cv2.waitKey(0)
grabcut算法是微软的一个研究院提出的。主要功能是分割和抠图,该算法利用了图像中的纹理(颜色)信息和边界(反差)信息,只要少量的用户交互操作即可得到比较好的分割结果。通俗的说,一开始用户用户需要用一个矩形将前景区域框住。然后使用算法迭代分割。但有时分割的结构不够理想,会把前景和背景弄错,这时需要我们人为的修正了。
这个可以说是非常好用的了,背后的算法理论我们就不写了,网上都有(我也写不明白啊)。这里该大家写明白怎么用就行了。
cv2.grabCut(img, mask, rect, bgdModel, fgdModel, iterCount, mode)
参数img:待分割的源图像,必须是8位3通道,在处理的过程中不会被修改
参数mask:掩码图像,如果使用掩码进行初始化,那么mask保存初始化掩码信息;在执行分割的时候,也可以将用户交互所设定的前景与背景保存到mask中,然后再传入grabCut函数;在处理结束之后,mask中会保存结果。mask只能取以下四种值:
(PS:如果没有手工标记GCD_BGD或者GCD_FGD,那么结果只会有GCD_PR_BGD或GCD_PR_FGD)
GCD_BGD(=0),背景
GCD_FGD(=1),前景
GCD_PR_BGD(=2),可能的背景
GCD_PR_FGD(=3),可能的前景
参数rect:用于限定需要进行分割的图像范围,只有该矩形窗口内的图像部分才被处理(矩形的形式为(x, y, w, h) )
参数bgdModel:背景模型,如果为None,函数内部会自动创建一个bgdModel;bgdModel必须是单通道浮点型图像,且行数只能为1,列数只能为13x5
参数fgdModel:前景模型,如果为None,函数内部会自动创建一个fgdModel;fgdModel必须是单通道浮点型图像,且行数只能为1,列数只能为13x5
参数iterCount:迭代次数,必须大于0,次数越多,效果越好(但消耗的时间就越多);
参数mode:用于指示grabCut函数进行什么操作,可选的值有:
GC_INIT_WITH_RECT(=0),用矩形窗初始化GrabCut
GC_INIT_WITH_MASK(=1),用掩码图像初始化GrabCut
GC_EVAL(=2),执行分割。
我来总结一下,首先就是你需要生成mask,bgdModel,fgdModel这两个可以选择不设置,然后就是设置目标区域的矩形框。一切参数设置好后,cv2.grabCut会返回mask,用来提取目标。(配合一下程序应该就看懂了)。
PS:还有就是,有时候提取的目标可能并不完美,这是就差需要校正,至于怎么校正,我试了一些方法,没搞明白,哈哈。不过下面我会推荐一个大佬写的这方法,感兴趣的小伙伴可以看看。
大佬写的
下面有用到numpy.where函数
numpy.where函数用法
import numpy as np
import cv2
img = cv2.imread('knife.jpg')
original=img.copy()
mask = np.zeros(img.shape[:2],np.uint8)
#bgdModel = np.zeros((1,65),np.float64)
#fgdModel = np.zeros((1,65),np.float64)
#矩形窗口(x,y,w,h);
rect = (30,144,465,133)#划定区域,需要使用电脑画图工具获得大概的位置
cv2.grabCut(img,mask,rect,None,None,5,cv2.GC_INIT_WITH_RECT)#函数返回值为mask,这里的迭代次数设置为5,刚好。
#cv2.grabCut(img,mask,rect,bgdModel,fgdModel,5,cv2.GC_INIT_WITH_RECT)#函数返回值为mask,bgdModel,fgdModel
mask1 = np.where((mask==2)|(mask==0),0,1).astype('uint8')#0和2做背景
cv2.rectangle(original,(40,144),(495,277),(0,255,0),3)#是原谅色(在原图上画出矩形窗口)
out_put=cv2.bitwise_and(img,img,mask=mask1)#使用蒙板来获取前景区域
cv2.imshow('original',original)
cv2.imshow('result',img)
cv2.waitKey(0)
这样手动输入矩形边框是不是觉得很low,下面来一个高级一点的写法(我也是在别的大佬基础之上改的),看看吧
import numpy as np
import cv2
# 鼠标事件的回调函数
def mouse(event, x, y, flag, param):#用来返回坐标
global rect
global leftButtonDowm
global leftButtonUp
# 鼠标左键按下
if event == cv2.EVENT_LBUTTONDOWN:
rect[0] = x
rect[2] = x
rect[1] = y
rect[3] = y
leftButtonDowm = True
leftButtonUp = False
# 移动鼠标事件
elif event == cv2.EVENT_MOUSEMOVE:
if leftButtonDowm and not leftButtonUp:
rect[2] = x
rect[3] = y
# 鼠标左键松开
elif event == cv2.EVENT_LBUTTONUP:
if leftButtonDowm 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
leftButtonDowm = False
leftButtonUp = True
def draw_rectangle():
img_copy = img.copy()
# 在img图像上,绘制矩形 线条颜色为green 线宽为2
cv2.rectangle(img_copy, (rect[0], rect[1]), (rect[2], rect[3]), (0, 255, 0), 2)
# 显示图片
cv2.imshow('img', img_copy)
def grabcut_process(img, mask, rectangle, itercount, mode):#这里忽略bgdModel,fgdModel
cv2.grabCut(img, mask, rectangle, None,None, itercount, mode)
mask1 = np.where((mask == 2) | (mask == 0), 0, 1).astype('uint8')
img_show=cv2.bitwise_and(img,img,mask=mask1)
return img_show
# 读入图片
img = cv2.imread('knife.jpg')
# 掩码图像,如果使用掩码进行初始化,那么mask保存初始化掩码信息;在执行分割的时候,也可以将用户交互所设定的前景与背景保存到mask中,然后再传入grabCut函数;在处理结束之后,mask中会保存结果
mask = np.zeros(img.shape[:2], np.uint8)
# 背景模型,如果为None,函数内部会自动创建一个bgdModel;bgdModel必须是单通道浮点型图像,且行数只能为1,列数只能为13x5;
#bgdModel = np.zeros((1, 65), np.float64)
# fgdModel——前景模型,如果为None,函数内部会自动创建一个fgdModel;fgdModel必须是单通道浮点型图像,且行数只能为1,列数只能为13x5;
#fgdModel = np.zeros((1, 65), np.float64)
# 用于限定需要进行分割的图像范围,只有该矩形窗口内的图像部分才被处理;
rect = [0, 0, 0, 0]
# 鼠标左键按下
leftButtonDowm = False
# 鼠标左键松开
leftButtonUp = True
# 指定窗口名来创建窗口
cv2.namedWindow('img')
# 设置鼠标事件回调函数 来获取鼠标输入
cv2.setMouseCallback('img', mouse)
# 显示图片
cv2.imshow('img', img)
while cv2.waitKey(1) == -1:#如果waitKey()里面的值设置的较大就没办法画到实时画矩形框了
# 左键按下,画矩阵
if leftButtonDowm and not leftButtonUp:
draw_rectangle()
# 左键松开,矩形画好
elif not leftButtonDowm and rect[0] != 0:#这一步是必须的
rect[2] = rect[2] - rect[0]
rect[3] = rect[3] - rect[1]
rect = tuple(rect)
out_put=grabcut_process(img, mask, rect, 5, cv2.GC_INIT_WITH_RECT)
# 显示原图
cv2.imshow('img', img)
# 显示图片分割后结果
cv2.imshow('grabcut', out_put)
rect = [0, 0, 0, 0] # 这一步是必须的,否则报错,让其与条件相悖
cv2.waitKey(0)
cv2.destroyAllWindows()
学习opencv有很多的方法,我的建议是你可以加一些群,可以充分利用B站,CSDN,和百度。
在我的博客中,我不会讲解opencv的算法实现(当然我也不太会),我只会讲解一些函数的调用,不理解就多改一些参数,多尝试尝试,慢慢你就理解来。相信你总有一天可以说opencv不过“Ctrl+C,Crtl+V”
PS:如果有什么错误的地方,还请大家批评指正,不过错了也没啥关系,反正也没什么人看
最后,希望小伙伴们都能有所收获。码字不易,喜欢的话,关注一波在走吧