【问题标题】:Edge detection for image stored in matrix存储在矩阵中的图像的边缘检测
【发布时间】:2015-04-03 14:54:19
【问题描述】:

我以二维数组的形式表示图像。我有这张照片:

如何获取直接位于灰色区域边界的像素并对其进行着色?

我想分别获取绿色和红色矩阵元素的坐标。我在矩阵上只有白色、黑色和灰色区域。

【问题讨论】:

  • 矩阵通常不是表示二维像素数据的好方法......这不是它们的语义目标。
  • @Sneftel 实际上,矩阵是在 OpenCV、SimpleCV 等库中表示图像的最常见的方式,即使不是唯一也是最好的方式。
  • 您将矩阵与二维数组混淆了。前者配备了一组非常具体的操作,这些操作对图像数据完全没有意义。
  • 试试 Sobel 算子 - 这是一个非常非常简单的算法。 youtube.com/watch?v=iendD-Iqoog

标签: python algorithm numpy edge-detection


【解决方案1】:

希望以下内容可以满足您的需求(或至少有所帮助)。想法是使用基于阈值的逻辑检查将其拆分为各个区域。然后可以使用 numpy roll 来检测这些区域之间的边缘以移动 x 和 y 中的像素并进行比较以查看我们是否处于边缘,

import matplotlib.pyplot as plt
import numpy as np
import scipy as sp
from skimage.morphology import closing

thresh1 = 127
thresh2 = 254

#Load image
im = sp.misc.imread('jBD9j.png')

#Get threashold mask for different regions
gryim = np.mean(im[:,:,0:2],2)
region1 =  (thresh1<gryim)
region2 =  (thresh2<gryim)
nregion1 = ~ region1
nregion2 = ~ region2

#Plot figure and two regions
fig, axs = plt.subplots(2,2)
axs[0,0].imshow(im)
axs[0,1].imshow(region1)
axs[1,0].imshow(region2)

#Clean up any holes, etc (not needed for simple figures here)
#region1 = sp.ndimage.morphology.binary_closing(region1)
#region1 = sp.ndimage.morphology.binary_fill_holes(region1)
#region1.astype('bool')
#region2 = sp.ndimage.morphology.binary_closing(region2)
#region2 = sp.ndimage.morphology.binary_fill_holes(region2)
#region2.astype('bool')

#Get location of edge by comparing array to it's 
#inverse shifted by a few pixels
shift = -2
edgex1 = (region1 ^ np.roll(nregion1,shift=shift,axis=0))
edgey1 = (region1 ^ np.roll(nregion1,shift=shift,axis=1))
edgex2 = (region2 ^ np.roll(nregion2,shift=shift,axis=0)) 
edgey2 = (region2 ^ np.roll(nregion2,shift=shift,axis=1))

#Plot location of edge over image
axs[1,1].imshow(im)
axs[1,1].contour(edgex1,2,colors='r',lw=2.)
axs[1,1].contour(edgey1,2,colors='r',lw=2.)
axs[1,1].contour(edgex2,2,colors='g',lw=2.)
axs[1,1].contour(edgey2,2,colors='g',lw=2.)
plt.show()

这给出了。为简单起见,我将 roll 与每个区域的倒数一起使用。您可以将每个连续区域滚动到下一个区域以检测边缘

感谢@Kabyle 提供奖励,这是我花了一段时间寻找解决方案的问题。我尝试了 scipy 骨架化、feature.canny、拓扑模块和 openCV,但成功有限……这种方式对我的案例来说是最稳健的(液滴界面跟踪)。希望对您有所帮助!

【讨论】:

  • 对不起,刚刚看到你想要索引,这些可以使用类似np.ma.nonzero(~edgex1)
  • 非常感谢您付出的巨大努力,我会尝试您的解决方案,看看它是否有效
  • 这是一个很好的尝试,但是看看在白色区域中间的灰色小岛周围会发生什么......而且滚动在图像的边缘有一点问题。
  • 将不同的区域相互滚动可以解决问题。由于滚动是循环的,您可以在边缘获得值,这些值可以很容易地修补(如果需要)。在 scipy、openCV 等中有一整套专用于边缘跟踪的工具,因此确保可以提供更详细的解决方案......基于 OPs 问题,上述解决方案似乎足够了,因为它很健壮并且不需要任何高水平scipy 工具。
【解决方案2】:

对此有一个非常简单的解决方案:根据定义,任何同时具有白色和灰色邻居的像素都位于您的“红色”边缘,而灰色和黑色邻居位于“绿色”边缘。最亮/最暗的邻居由skimage.filters.rank 中的最大/最小过滤器返回,具有最亮/最暗邻居(白色/灰色或灰色/黑色)的像素掩码的二进制组合分别产生边缘。

结果:

一个可行的解决方案:

import numpy
import skimage.filters.rank
import skimage.morphology
import skimage.io

# convert image to a uint8 image which only has 0, 128 and 255 values
# the source png image provided has other levels in it so it needs to be thresholded - adjust the thresholding method for your data
img_raw = skimage.io.imread('jBD9j.png', as_grey=True)
img = numpy.zeros_like(img, dtype=numpy.uint8)
img[:,:] = 128
img[ img_raw < 0.25 ] = 0
img[ img_raw > 0.75 ] = 255

# define "next to" - this may be a square, diamond, etc
selem = skimage.morphology.disk(1)

# create masks for the two kinds of edges
black_gray_edges = (skimage.filters.rank.minimum(img, selem) == 0) & (skimage.filters.rank.maximum(img, selem) == 128)
gray_white_edges = (skimage.filters.rank.minimum(img, selem) == 128) & (skimage.filters.rank.maximum(img, selem) == 255)

# create a color image
img_result = numpy.dstack( [img,img,img] )

# assign colors to edge masks
img_result[ black_gray_edges, : ] = numpy.asarray( [ 0, 255, 0 ] )
img_result[ gray_white_edges, : ] = numpy.asarray( [ 255, 0, 0 ] )

imshow(img_result)

附:具有黑白邻居或所有三种颜色邻居的像素属于未定义的类别。上面的代码不会为这些着色。您需要弄清楚在这些情况下如何为输出着色;但是很容易扩展上述方法以为此制作另一个或两个掩码。

附:边缘是两个像素宽。没有更多信息就无法解决:边缘位于两个区域之间,并且您尚未定义希望它们在每种情况下重叠的两个区域中的哪一个,因此唯一对称的解决方案是将两个区域重叠一个像素。

附:这会将像素本身视为其自己的邻居。灰色上孤立的白色或黑色像素,反之亦然,将被视为边缘(以及它周围的所有像素)。

【讨论】:

    【解决方案3】:

    虽然 plonser 的回答可能很容易实施,但我认为它在锋利和薄边方面失败了。不过,我建议您使用他的部分方法作为预处理。
    在第二步中,您想使用Marching Squares Algorithm。根据scikit-image的文档,是

    行进立方体算法的一个特例(Lorensen、William 和 哈维·E·克莱恩。 Marching Cubes:高分辨率 3D 表面 构造算法。计算机图形学(SIGGRAPH 87 论文集) 21(4) 1987 年 7 月,第 21 页。 163-170

    甚至还有一个 Python 实现作为 scikit-image 包的一部分。我一直在成功地使用这个算法(不过是我自己的 Fortran 实现)在通信工程中对眼图进行边缘检测。

    广告 1:预处理
    创建图像的副本并使其仅具有两种颜色,例如黑,白。坐标保持不变,但您要确保算法能够正确地做出是/否决定,而与您在图像矩阵表示中使用的值无关。

    广告 2:边缘检测
    维基百科以及各种博客都为您提供了各种语言的算法的漂亮elaborate description,因此我不再赘述。不过,让我给你一些实用的建议:

    1. 您的图像底部有开放边界。您可以人为地添加另一行像素(黑色或灰色来限制白色/灰色区域),而不是修改算法。
    2. 起点的选择至关重要。如果要处理的图像不是太多,建议您手动选择它。否则,您将需要定义规则。由于 Marching Squares 算法可以在有界区域内的任何位置开始,因此您可以选择给定颜色/值的任何像素来检测相应的边缘(它最初会开始沿一个方向行走以找到边缘)。
    3. 算法返回精确的 2D 位置,例如(x/y)-元组。你可以
      • 遍历列表并通过分配不同的值或为相应的像素着色
      • 创建一个mask 来选择矩阵的各个部分并分配对应于不同颜色的值,例如绿色或红色。

    最后:一些后期处理
    我建议为图像添加人工边界。这有两个优点: 1. Marching Squares 算法开箱即用。 2. 无需区分图像边界和图像内两个区域之间的界面。设置完彩色边缘后,只需移除人工边界 - 这将移除图像边界处的彩色线条。

    【讨论】:

      【解决方案4】:

      基本上按照pyStarter的建议使用scikit-image中的marching square算法,可以使用以下代码提取所需的轮廓:

      import matplotlib.pyplot as plt
      import numpy as np
      import scipy as sp
      from skimage import measure
      import scipy.ndimage as ndimage
      from skimage.color import rgb2gray
      from pprint import pprint
      #Load image
      im = rgb2gray(sp.misc.imread('jBD9j.png'))
      
      n, bins_edges = np.histogram(im.flatten(),bins = 100)
      # Skip the black area, and assume two distinct regions, white and grey
      max_counts = np.sort(n[bins_edges[0:-1] > 0])[-2:]
      thresholds = np.select(
          [max_counts[i] == n for i in range(max_counts.shape[0])],
          [bins_edges[0:-1]] * max_counts.shape[0]
      )
      # filter our the non zero values
      thresholds = thresholds[thresholds > 0] 
      
      
      fig, axs = plt.subplots()
      # Display image
      axs.imshow(im, interpolation='nearest', cmap=plt.cm.gray)
      colors = ['r','g']
      for i, threshold in enumerate(thresholds):
          contours = measure.find_contours(im, threshold)
      
          # Display  all contours found for this threshold
          for n, contour in enumerate(contours):
              axs.plot(contour[:,1], contour[:,0],colors[i], lw = 4)
      
      axs.axis('image')
      axs.set_xticks([])
      axs.set_yticks([])        
      plt.show()
      

      !

      但是,从您的图像中没有明确定义的灰色区域,因此我获取了图像中两个最大的强度计数,并对它们进行了阈值处理。有点令人不安的是白色区域中间的红色区域,但是我认为这可以通过直方图过程中的 bin 数量进行调整。您也可以像 Ed Smith 那样手动设置这些。

      【讨论】:

        【解决方案5】:

        也许有更优雅的方式来做到这一点...... 但如果您的数组是 numpy 数组,尺寸为 (N,N)(灰度),您可以这样做

        import numpy as np
        
        # assuming black -> 0 and white -> 1 and grey -> 0.5
        black_reg = np.where(a < 0.1, a, 10)
        white_reg = np.where(a > 0.9, a, 10)
        
        xx_black,yy_black = np.gradient(black_reg)
        xx_white,yy_white = np.gradient(white_reg)
        
        # getting the coordinates
        coord_green = np.argwhere(xx_black**2 + yy_black**2>0.2)
        coord_red   = np.argwhere(xx_white**2 + yy_white**2>0.2)
        

        0.2这个数字只是一个阈值,需要调整。

        【讨论】:

        • 你有没有考虑到我只有第一张图片上有黑色、白色和绿色? (是的,我只寻找绿色区域边界的坐标)
        • @begueradj:哦……对不起。这需要一点修改。黑色是对应数字0还是1
        • 您可以设置任何约定来定义黑白和灰色元素
        【解决方案6】:

        我认为您可能正在寻找灰度图像的边缘检测方法。有很多方法可以做到这一点。也许这可以帮助http://en.m.wikipedia.org/wiki/Edge_detection。要区分白色和灰色之间的边缘以及黑色和灰色之间的边缘,请尝试使用局部平均强度。

        【讨论】:

          猜你喜欢
          • 2019-05-16
          • 2010-12-01
          • 2011-06-25
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多