Python经典案例之图像漫水填充分割详解

 

一.图像漫水填充

图像漫水填充(FloodFill)是指用一种特定的颜色填充联通区域,通过设置可连通像素的上下限以及连通方式来达到不同的填充效果。漫水填充通常被用来标记或分离图像的一部分以便对其进行深入的处理或分析。

图像漫水填充主要是遴选出与种子点联通且颜色相近的像素点,接着对像素点的值进行处理。如果遇到掩码,则根据掩码进行处理。其原理类似Photoshop的魔术棒选择工具,漫水填充将查找和种子点联通的颜色相同的点,而魔术棒选择工具是查找和种子点联通的颜色相近的点,将和初始种子像素颜色相近的点压进栈作为新种子。基本工作步骤如下:

  • 选定种子点(x,y);
  • 检查种子点的颜色,如果该点颜色与周围连接点的颜色不相同,则将周围点颜色设置为该点颜色;如果相同则不做处理。但是周围点不一定都会变成和种子点的颜色相同,如果周围连接点在给定的范围(从loDiff到upDiff)内或在种子点的像素范围内才会改变颜色;
  • 检测其他连接点,进行第2个步骤的处理,直到没有连接点,即到达检测区域边界停止。

 

二.图像漫水填充分割实现

在OpenCV中,主要通过floodFill()函数实现漫水填充分割,它将用指定的颜色从种子点开始填充一个连接域。其函数原型如下所示:

floodFill(image, mask, seedPoint, newVal[, loDiff[, upDiff[, flags]]])

– image表示输入/输出1通道或3通道,6位或浮点图像

– mask表示操作掩码,必须为8位单通道图像,其长宽都比输入图像大两个像素点。注意,漫水填充不会填充掩膜mask的非零像素区域,mask中与输入图像(x,y)像素点相对应的点的坐标为(x+1,y+1)。

– seedPoint为Point类型,表示漫水填充算法的起始点

– newVal表示像素点被染色的值,即在重绘区域像素的新值

– loDiff表示当前观察像素值与其部件邻域像素值或待加入该部件的种子像素之间的亮度或颜色之负差的最大值,默认值为Scalar( )

– upDiff表示当前观察像素值与其部件邻域像素值或待加入该部件的种子像素之间的亮度或颜色之正差的最大值,默认值为Scalar( )

– flags表示操作标识符,此参数包括三个部分:低八位0-7bit表示邻接性(4邻接或8邻接);中间八位8-15bit表示掩码的填充颜色,如果中间八位为0则掩码用1来填充;高八位16-31bit表示填充模式,可以为0或者以下两种标志符的组合,FLOODFILL_FIXED_RANGE表示此标志会考虑当前像素与种子像素之间的差,否则就考虑当前像素与相邻像素的差。FLOODFILL_MASK_ONLY表示函数不会去填充改变原始图像,而是去填充掩码图像mask,mask指定的位置为零时才填充,不为零不填充。

在Python和OpenCV实现代码中,它设置种子点位置为(10,200);设置颜色为黄色(0,255,255);连通区范围设定为loDiff和upDiff;标记参数设置为CV_FLOODFILL_FIXED_RANGE ,它表示待处理的像素点与种子点作比较,在范围之内,则填充此像素,即种子漫水填充满足:

src(seed.x, seed.y) - loDiff <= src(x, y) <= src(seed.x, seed.y) +upDiff

完整代码如下:

# -*- coding: utf-8 -*-
# By: Eastmount
import cv2
import numpy as np

#读取原始图像
img = cv2.imread('windows.png')

#获取图像行和列
rows, cols = img.shape[:2]

#目标图像
dst = img.copy()

#mask必须行和列都加2且必须为uint8单通道阵列
#mask多出来的2可以保证扫描的边界上的像素都会被处理
mask = np.zeros([rows+2, cols+2], np.uint8)  

#图像漫水填充处理
#种子点位置(30,30) 设置颜色(0,255,255) 连通区范围设定loDiff upDiff
#src(seed.x, seed.y) - loDiff <= src(x, y) <= src(seed.x, seed.y) +upDiff
cv2.floodFill(dst, mask, (30, 30), (0, 255, 255),
            (100, 100, 100), (50, 50, 50),
            cv2.FLOODFILL_FIXED_RANGE)

#显示图像
cv2.imshow('src', img)
cv2.imshow('dst', dst)
cv2.waitKey()
cv2.destroyAllWindows()

输出结果如图1所示,左边为原始图像,右边为将Windows图标周围填充为黄色的图像。

 

三.图像漫水填充分割自动软件

下面补充另一段代码,它将打开一幅图像,点击鼠标选择种子节点,移动滚动条设定连通区范围的loDiff和upDiff值,并产生动态的漫水填充分割。

注意,该部分代码中涉及鼠标、键盘、滚动条等操作,希望读者下来学习相关知识,该系列文章更多是讲解Python图像处理的算法原理及代码实现。

# coding:utf-8
import cv2
import random
import sys
import numpy as np

#使用说明 点击鼠标选择种子点
help_message = '''USAGE: floodfill.py [<image>]
Click on the image to set seed point
Keys:
f - toggle floating range
c - toggle 4/8 connectivity
ESC - exit
'''

if __name__ == '__main__':

  #输出提示文本
  print(help_message)

  #读取原始图像
  img = cv2.imread('scenery.png')

  #获取图像高和宽
  h, w = img.shape[:2]

  #设置掩码 长和宽都比输入图像多两个像素点 
  mask = np.zeros((h+2, w+2), np.uint8)

  #设置种子节点和4邻接
  seed_pt = None
  fixed_range = True
  connectivity = 4 

  #图像漫水填充分割更新函数
  def update(dummy=None):
      if seed_pt is None:
          cv2.imshow('floodfill', img)
          return
      
      #建立图像副本并漫水填充
      flooded = img.copy()
      mask[:] = 0 #掩码初始为全0
      lo = cv2.getTrackbarPos('lo', 'floodfill') #像素邻域负差最大值
      hi = cv2.getTrackbarPos('hi', 'floodfill') #像素邻域正差最大值
      print('lo=', lo, 'hi=', hi)

      #低位比特包含连通值 4 (缺省) 或 8
      flags = connectivity
      
      #考虑当前像素与种子像素之间的差(高比特也可以为0)
      if fixed_range:
          flags |= cv2.FLOODFILL_FIXED_RANGE
          
      #以白色进行漫水填充
      cv2.floodFill(flooded, mask, seed_pt,
                    (random.randint(0,255), random.randint(0,255),
                     random.randint(0,255)), (lo,)*3, (hi,)*3, flags)

      #选定基准点用红色圆点标出
      cv2.circle(flooded, seed_pt, 2, (0, 0, 255), -1)
      print("send_pt=", seed_pt)

      #显示图像
      cv2.imshow('floodfill', flooded)

  #鼠标响应函数
  def onmouse(event, x, y, flags, param):
      global seed_pt #基准点

      #鼠标左键响应选择漫水填充基准点
      if flags & cv2.EVENT_FLAG_LBUTTON:
          seed_pt = x, y
          update()

  #执行图像漫水填充分割更新操作
  update()
  
  #鼠标更新操作
  cv2.setMouseCallback('floodfill', onmouse)

  #设置进度条
  cv2.createTrackbar('lo', 'floodfill', 20, 255, update)
  cv2.createTrackbar('hi', 'floodfill', 20, 255, update)

  #按键响应操作
  while True:
      ch = 0xFF & cv2.waitKey()
      #退出
      if ch == 27:
          break
      #选定时flags的高位比特位0
      #邻域的选定为当前像素与相邻像素的差, 联通区域会很大
      if ch == ord('f'):
          fixed_range = not fixed_range 
          print('using %s range' % ('floating', 'fixed')[fixed_range])
          update()
      #选择4方向或则8方向种子扩散
      if ch == ord('c'):
          connectivity = 12-connectivity 
          print('connectivity =', connectivity)
          update()
  cv2.destroyAllWindows()

当鼠标选定的种子点为(242,96),观察点像素邻域负差最大值“lo”为138,观察点像素邻域正差最大值“hi”为147时,图像漫水填充效果如图2所示,它将天空和中心水面填充成黄色。

当鼠标选定的种子点为(328, 202),观察点像素邻域负差最大值“lo”为142,观察点像素邻域正差最大值“hi”为45时,图像漫水填充效果如图3所示,它将图像两旁的森林和水面填充成蓝紫色。

 

四.总结

写到这里,图像分割知识点就介绍完毕,包括基于阈值的图像分割方法、基于边缘检测的图像分割方法、基于纹理背景的图像分割方法和基于特定理论的图像分割方法。其中,基于特定理论的分割方法又分别讲解了基于K-Means聚类、均值漂移、分水岭算法的图像分割方法。最后通过漫水填充分割案例加深了读者的印象。希望读者能结合本章知识点,围绕自己的研究领域或工程项目进行深入的学习,实现所需的图像处理。

以上就是Python经典案例之图像漫水填充分割详解的详细内容,更多关于Python图像漫水填充分割的资料请关注编程宝库其它相关文章!

 tkinter实现打开文件对话框并获取文件绝对路径# 首先,导入模块import tkinter.filedialog# 此处省略父容器的定义 ...# 第2步,定义按钮并 ...