opencv-python 小白笔记(15)

第十五节:Otsu阈值,获取图形的中心,GrabCut抠图

  • (一)Otsu阈值
  • (二)获取图形的中心
  • (三)GrabCut抠图
  • (四)结语

(一)Otsu阈值

这里写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抠图

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')#02做背景

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:如果有什么错误的地方,还请大家批评指正,不过错了也没啥关系,反正也没什么人看
最后,希望小伙伴们都能有所收获。码字不易,喜欢的话,关注一波在走吧

你可能感兴趣的:(opencv-python笔记,python,opencv)