基于python中cv2库的图像分割

Posted 晴一千天

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了基于python中cv2库的图像分割相关的知识,希望对你有一定的参考价值。

一.概念

1.简述Radon变换检测直线的原理。

        Radon变换就是将数字图像矩阵在某一角度射线方向上做投影变换,按照投影方向累加像素。

2.简述基于区域生长的图像分割的原理。

        根据同一物体区域内像素相似性质来聚集像素点。选定图像中的一个点(或一组点),从初始起点开始,将相邻的具有同样性质的像素或归并到目前的区域中从而逐步增长区域,直到没有可以归并的点为止。

3. 简述基于分水岭算法进行图像分割的原理。

        将图像看作拓扑地貌,图像中的每一点像素的灰度值表示该点的海拔高度,每一局部极小值及其影响区域就是集水盆地,集水盆地的边界则形成分水岭。在每一个局部最小值表面刺穿一个孔,然后向模型里注水,随着水量增多,局部最小值的影响逐渐向外扩展,而集水盆间交界处筑成大坝即形成分水岭。

 

 4.如何计算一个区域内部的一致性或者平滑性

方法一:Sobel算子利用像素邻近区域的梯度值来计算一个像素的梯度,再根据一定的绝对值来判断其是不是噪声点。

方法二:计算图像一块区域的像素平均值,再算出该区域内每个点像素值与均值的差绝对值,设定阈值,大于某个阈值则该点算为噪声点。

5.如何计算两个区域之间的相似性或者距离

方法一:Kolmogorov-Smirnov检验:统计两个区域0-255每个像素值出现的次数,比较像数值的频率分布,两个区域间每个元素对应一个频率差值,如果最大的频率差值小于规定的值,则两区域相似,反之不相似。

方法二:通过直方图余弦值计算相似性。将两区域像素点的灰度值统计成直方图,压缩灰度级别,每间隔四个数合为一个分向量,共分为64个分向量,记作一个向量;计算2个区域向量夹角的余弦值,作为相似度。

 二.编程题

1.图像分割的目的是把图像分割成若干区域。请查阅相关文档,理解如何计算分割算法准确率。写出函数实现该方法。

1.1平均交并比原理:

 

 

1.2 函数实现:

def create_hist(a,b,n):
    """
    :param a: 形状为H*W的预测值(H为高,W为宽)
    :param b: 形状为H*W的真实值(H为高,W为宽)
    :param n: 类别数
    :return: 混淆矩阵
    """
    #确保a和b在0~n-1的范围内
    k = (a>=0)&(a<n)
    return np.bincount(n*a[k].astype(int) + b[k],minlength=n**2).reshape(n,n)

def per_iou(hist):
    """
    :param hist:传入混淆矩阵(n,n)
    :return: 每个类别的iou
    """
    #因为交并比有除法,防止分母出现0报错
    np.seterr(divide="ignore",invalid="ignore")
    #交集:np.diag取对角线元素
    #并集:hist.sum(0)和hist.sum(1)两个维度相加,减去多加了一次的对角线元素
    iou = np.diag(hist) / (hist.sum(0)+hist.sum(1)-np.diag(hist))
    #把报错调回来
    np.seterr(divide="warn",invalid="warn")
    #分母为0时结果是Nan,将Nan值改为0
    iou[np.isnan(iou)] = 0
    return iou

2 .针对shapes.png, 利用颜色信息把图像进行分割,把结果作为分割的标准答案。

2.1流程

 

2.2代码:

#生成初始种子
def originaSeed(gray):
    """
    :param gray: 传入灰度图
    :return:  连通域中心
    """
    ret,img1 = cv2.threshold(gray,245,255,cv2.THRESH_BINARY)#二值化
    num_labels,labels,stats,centroids = cv2.connectedComponentsWithStats(img1)#进行连通域连通
    centroids = centroids.astype(int)
    return centroids

#求两个点像素值差值
def Graydiff(gray,currentpoint,tmppoint):
    return abs(int(gray[currentpoint[0],currentpoint[1]])-int(gray[tmppoint[0],tmppoint[1]]))

#区域生长
def regional_growth(gray,seeds):
    """
    :param gray:灰度图片
    :param seeds:初始种子,连通域中心
    :return:图片矩阵
    """
    #像素种子的8个邻接点
    approximal_point = [(-1,-1),(0, -1), (1, -1), (1, 0),
                        (1, 1), (0, 1), (-1, 1), (-1, 0)]
    threshold = 5 #生长相似性的阈值,Graydiff不超过5就符合
    height,weight = gray.shape
    img = np.zeros(gray.shape)#创建等灰度图尺寸的矩阵,满足生长规则的点改为1
    seedlist = []
    for seed in seeds:
        #种子位置大于0且不超过图片尺寸
        if (seed[0] < gray.shape[0] and seed[1] < gray.shape[1] and seed[0] > 0 and seed[1] > 0):
            seedlist.append(seed)
    while len(seedlist)>0:#循环到列表中没有种子点
        currentPoint = seedlist.pop(0)#最新的种子点等于列表里的第一个种子点,同时取出第一个点后删除该点做到列表更新
        img[currentPoint[0],currentPoint[1]] = 1#找到对应位置把0改为1
        #对该位置8领域进行象素差判断
        for i in range(8):
            tmpX = currentPoint[0] + approximal_point[i][0]
            tmpY = currentPoint[1] + approximal_point[i][1]
            if tmpX < 0 or tmpY <0 or tmpX >= height or tmpY >= weight:#如果位置值超过图片大小或者非正数
                continue#跳过并继续
            grayDiff = Graydiff(gray,currentPoint,(tmpX,tmpY))#计算像素差
            if grayDiff < threshold and img[tmpX,tmpY]==0:#如果小于阈值并且矩阵该点为0
                img[tmpX,tmpY] = 1
                seedlist.append((tmpX,tmpY))
    return img

def original_growth():
    img = cv2.imread('E:\\\\Machine vision\\\\images\\\\images\\\\shapes.png')
    img = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
    o_img = copy.deepcopy(img)
    seed = originaSeed(img)
    img = regional_growth(img,seed)

    #显示区域生长效果
    plt.rcParams['font.sans-serif'] = ['SimHei']#正常显示中文标签
    plt.figure(figsize=(10, 5))
    plt.subplot(121), plt.imshow(o_img, cmap='gray'), plt.title('区域生长前'),plt.axis("off")
    plt.subplot(122),plt.imshow(img,cmap='gray'),plt.title('区域生长以后'),plt.axis("off")
    plt.show()
test_original_growth()

2.3原图及效果展示(左原右结果)

 

 

 3.针对shapes.png, 利用opencv的K-Means方法对图像进行分割,基于2.1和2.2计算分割准确率,并展示效果图。

3.1代码

def kmeans_segmentation():
    image = cv2.imread('E:\\\\Machine vision\\\\images\\\\images\\\\shapes.png')
    image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    #展成一维
    img1 = image.reshape((image.shape[0] * image.shape[1], 1))
    img1 = np.float32(img1)

    #迭代终止条件:精度满足0.2/迭代次数超过阈值100
    criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 100, 0.2)
    k = 3
    # cv2.kmeans返回紧密度、标签、聚类中心
    compactness, labels, (centers) = cv2.kmeans(img1, k, None, criteria, 10, cv2.KMEANS_RANDOM_CENTERS)#每次随机选择中心
    # 标签尺寸改变为原图尺寸
    img2 = labels.reshape((image.shape[0], image.shape[1]))
    plt.figure()
    plt.imshow(img2),plt.title('segmented image'),plt.axis('off')
    plt.show()
    return img2

def iou_test():
    true_img = np.int64(original_growth())
    predict_img = np.int64(kmeans_segmentation())
    hist = create_hist(predict_img,true_img,3)#获取混淆矩阵
    iou = per_iou(hist)#获取每一类的iou
    m_iou = np.sum(iou)
    result = m_iou/3#求预测的平均值
    return result
print("准确率为".format(iou_test()))

3.2效果图

 4.实现基于区域分裂合并的分割方法。请自行定义区域分裂和合并的准则,并在shapes.png 进行测试,计算出准确率,展示效果图。

4.1算法思想:

 

4.2代码

#判断区域是否需要再拆分为四个
def judge(img,x0,y0,w,h):
    arr = img[y0:y0+h,x0:x0+w]
    ave = np.mean(arr)#求平均
    std = np.std(arr)#计算标准差
    count = len(np.where(img - ave < std)[1])#判断区域相似度
    acc = w * h
    if count/acc<0.95:#判断该区域是否还需要再分
        return False
    else:
        return True

def recursion(img,x0,y0,w,h):#递归
    if judge(img,x0,y0,w,h) and (min(w,h)>5):#不用细分到每个像素点为一个区域
        recursion(img,x0,y0,int(w/2),int(h/2))
        recursion(img,x0+int(w/2),y0,int(w/2),int(h/2))
        recursion(img,x0,y0+int(h/2),int(w/2),int(h/2))
        recursion(img,x0+int(w/2),y0+int(h/2),int(w/2),int(h/2))
    else:
        ret,img = cv2.threshold(img,125,255,cv2.THRESH_BINARY)
    return img

def rec_test():
    img = cv2.imread('E:\\\\Machine vision\\\\images\\\\images\\\\shapes.png')
    gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)#灰度转换
    cv2.imshow('input',gray)
    cv2.waitKey(0)
    height, width = gray.shape
    out_img = recursion(gray,0,0,width,height)
    cv2.imshow('output',out_img)
    cv2.waitKey(0)

5.写出代码,综合运用图像处理和分割的有关算法,在oranges.png中检测橘子,展示检测效果图

5.1过程

 

5.2代码

def oranges():
    img = cv2.imread('E:\\\\Machine vision\\\\images\\\\images\\\\oranges.png')  # 读取图片
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)  # 灰度图转化

    ret, thresh = cv2.threshold(gray, 0, 255,
                                cv2.THRESH_BINARY + cv2.THRESH_OTSU)  # 图像二值化,cv2.THRESH_BINARY+cv2.THRESH_OTSU作用是自适应阈值
    kernel = np.ones((5, 5), np.uint8)
    opening = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=2)  # 开操作:去除噪声
    sure_bg = cv2.dilate(opening, kernel, iterations=3)  # 腐蚀操作,减小前景物体

    dist_transform = cv2.distanceTransform(sure_bg, cv2.DIST_L2, 5)  # 距离变换:得到每个非零像素点与其最近的零像素点之间的距离,输出为距离
    ret, sure_fg = cv2.threshold(dist_transform, 0.53 * dist_transform.max(), 255,0)  # 二值化操作,阈值为距离变换得到的距离,分离前景中的粘连物体
    sure_fg = np.uint8(sure_fg)  # 转化数据类型,连通域函数接收8位单通道二值图像
    # 连通域函数:label与原图大小一致,对应为当前像素为第几个轮廓;stats对应轮廓信息,每行有五个值,分别为x,y,width,height,area;centroids对应每个连通区域的质心点
    retval, labels, stats, centroids = cv2.connectedComponentsWithStats(sure_fg)

    for i in range(1, len(centroids)):
        cv2.circle(img, (int(centroids[i, 0]), int(centroids[i, 1])), 6, [255, 0, 0], -1)  # 勾画质心点,实心圆
        cv2.putText(img, str(i), (int(centroids[i, 0]), int(centroids[i, 1])), cv2.FONT_HERSHEY_SIMPLEX, 0.5,(0, 255, 0))  # 标记对应质心点的编号,最大值为个数-1
    print('计数个数:', len(centroids) - 1)
    cv2.imshow('img',img)
    cv2.waitKey(0)
oranges()

5.3原图及效果展示

 

以上是关于基于python中cv2库的图像分割的主要内容,如果未能解决你的问题,请参考以下文章

OpenCV图像处理之——分水岭算法的图像分割

图像语义分割出的json文件和原图,用plt绘制图像mask

在 python 中使用 OpenCV 分割图像

OpenCV—Python 分水岭算法图像分割

Python+OpenCV图像处理之对象测量

在opencv中,如何把灰度图像中的黑和白快速转化为1和0;