8wDlpd.png
8wDFp9.png
8wDEOx.png
8wDMfH.png
8wDKte.png

如何使用 OpenCV 连接图像中的边缘?

ryandra 2月前

164 0

我有一张二进制图像,其中包含一些矩形形状,其中只有边缘完全可见。我想连接缺失的线条并制作完整的矩形。如何使用 Open... 实现此目的

我有一张二值图像,其中包含一些矩形形状,其中只有边缘完全可见。我想连接缺失的线条并制作完整的矩形。如何使用 OpenCV 实现这一点。

source image

我的目标结果如下:

帖子版权声明 1、本帖标题:如何使用 OpenCV 连接图像中的边缘?
    本站网址:http://xjnalaquan.com/
2、本网站的资源部分来源于网络,如有侵权,请联系站长进行删除处理。
3、会员发帖仅代表会员个人观点,并不代表本站赞同其观点和对其真实性负责。
4、本站一律禁止以任何方式发布或转载任何违法的相关信息,访客发现请向站长举报
5、站长邮箱:yeweds@126.com 除非注明,本帖由ryandra在本站《opencv》版块原创发布, 转载请注明出处!
最新回复 (0)
  • 您需要检测每个角的方向。然后我会尝试使用其他两个角作为辅助,将左上角与右下角配对。一旦您知道配对,只需画 4 条线

  • @Elerium115 感谢您的评论。如果可以的话,您能分享一些代码片段吗?如何检测角的方向?

  • 抱歉,这是您需要实现的。您可以使用不同内核的卷积。有些内核是水平线检测器,有些是垂直线检测器。您可以创建自己的内核来检测 L 形。然后水平和垂直镜像内核以检测所有 4 种类型的角。看看

  • 找到那些角落符号。确定每个符号的方向。沿着预期的方向寻找合适的匹配。仅与沿轴的最近匹配相关联。关联。重复直到所有关联或没有可能的关联。你将得到一个由多个连通分量组成的图表,每个分量都是 4 循环,希望如此,也可能不是。

  • Mask 2月前 0 只看Ta
    引用 6

    这不是一项简单的任务。您需要识别图像中边界框的角,然后将它们匹配在一起,然后再绘制其余的矩形。第一部分通常使用卷积完成。有几个库可以处理这个问题: pattern_match 来自 scikit-image 是一个不错的开始。

    然而,在这个特殊情况下,角点是单值且宽度和高度均为一个像素。您可以通过在图像阵列上扫描窗口并匹配角点的外观来强力找到角点。这样就可以找到角点的位置。

    然后你需要匹配属于一起的角。左上角和右上角的 top-value 应该非常接近;左上角和左下角的 left-value 应该非常接近;等等。

    以下是我上面描述的两个步骤。首先,我们收集图像数据并将其转换为 numpy 数组。

    import cv2
    import io
    import numpy as np
    from PIL import Image
    import requests
    from collections import namedtuple
    
    # first collect our data and turn it into a numpy array
    res = requests.get('https://i.sstatic.net/cLDfS.png')
    img = Image.open(io.BytesIO(res.content))
    img_arr = np.array(img)
    
    # create objects to organize the data 
    CornerCollection = namedtuple('CornerCollection', ['tls', 'trs', 'brs', 'bls'])
    BoundingBox = namedtuple('BoundingBox', ['tl', 'tr', 'br', 'bl'])
    

    添加遍历图像数组并寻找与角落完全匹配的函数。

    def find_corners(img: np.array) -> CornerCollection:
        """Finds the corners outlines in a mono-tone image."""
        top_left = np.array([[1,1],[1,0]])
        top_right = np.array([[1,1],[0,1]])
        bot_right = np.array([[0,1],[1,1]])
        bot_left = np.array([[1,0],[1,1]])
    
        tl_corners = []
        tr_corners = []
        br_corners = []
        bl_corners = []
    
        window_iter = np.lib.stride_tricks.sliding_window_view(np.array(img), (2,2))
        for i, row_view in enumerate(window_iter):
            for j, view in enumerate(row_view):
                if (view == top_left).all():
                    tl_corners.append((i, j))
                if (view == top_right).all():
                    tr_corners.append((i, j + 1))
                if (view == bot_right).all():
                    br_corners.append((i + 1, j + 1))
                if (view == bot_left).all():
                    bl_corners.append((i, j + 1))
        return CornerCollection(tl_corners, tr_corners, br_corners, bl_corners)
    
    crnr_col = find_corners(img_arr)
    

    这里我们遍历每个左上角并找到与其他角的最佳匹配。这将返回一个 BoundingBox 对象列表。

    def merge_corners(cc: CornerCollection) -> list[BoundingBox]:
        boxes = []
        trs: list = cc.trs.copy()
        bls: list = cc.bls.copy()
        brs: list = cc.brs.copy()
        for (tl_i, tl_j) in cc.tls:
            best_tr = min(trs, key=lambda ij: abs(ij[0] - tl_i))
            best_bl = min(bls, key=lambda ij: abs(ij[1] - tl_j))
            best_br = min(brs, key=lambda ij: abs(ij[0] - tl_i) + abs(ij[1] - tl_j))
            trs.remove(best_tr)
            bls.remove(best_bl)
            brs.remove(best_br)
            boxes.append(BoundingBox((tl_i, tl_j), best_tr, best_br, best_bl))
        return boxes
    
    bboxes = merge_corners(crnr_col)
    bboxes
    # returns:
    [BoundingBox(tl=(9, 534), tr=(9, 545), br=(41, 545), bl=(41, 534)),
     BoundingBox(tl=(57, 672), tr=(57, 689), br=(80, 689), bl=(80, 672)),
     BoundingBox(tl=(77, 322), tr=(77, 340), br=(127, 340), bl=(127, 322)),
     BoundingBox(tl=(107, 849), tr=(107, 872), br=(143, 872), bl=(143, 849)),
     BoundingBox(tl=(132, 758), tr=(132, 794), br=(268, 794), bl=(268, 758)),
     BoundingBox(tl=(160, 916), tr=(160, 971), br=(298, 971), bl=(298, 916)),
     BoundingBox(tl=(169, 524), tr=(169, 539), br=(186, 539), bl=(186, 524)),
     BoundingBox(tl=(199, 581), tr=(199, 640), br=(325, 640), bl=(325, 581)),
     BoundingBox(tl=(244, 1155), tr=(244, 1191), br=(343, 1191), bl=(343, 1155))]
    

    使用 bboxes tl tr br 以及回到到的 bl 线条 tl 来制作矩形或者使用函数 cv2.rectangle

    import cv2
    
    for bbox in bboxes:
        cv2.rectangle(img_arr, bbox.tl[::-1], bbox.br[::-1], color=1, thickness=1)
    

    Here is the plot with the rectangles added.

  • 我想我也把“我的”方法扔进锅里。当我发表评论时,我还没有看到詹姆斯的回答。我的方法非常相似,增加了一些方向约束。我还假设角彼此完全成一线,即 它们形成的轴 偏移

    方法:

    • 使用 “命中和未命中变换” ,找到这些角标记,并按它们指示的角分组
    • 方向 上找到最接近匹配的角

    如果所有角度都匹配,这将找到所有的盒子。

    我将介绍两种将节点关联到矩形的方法。第一种方法更像“行人”,以固定顺序绕矩形移动。第二种方法构建一个图形,因此它可以更优雅地处理缺失的角落。

    我不处理只有两个角的矩形的情况。如果它们彼此对角,则可以恢复。我不这样做。这只需要朝正确的方向看并考虑最近的角...但这必须确保不会捕捉到实际上属于完整矩形的角。


    # hit-miss-transform
    
    kernel = np.array([
        [-1, -1, -1],
        [-1, +1, +1],
        [-1, +1, -1]
    ], dtype=np.int8)
    
    def find_corners(im, kernel):
        res = cv.morphologyEx(im, cv.MORPH_HITMISS, kernel)
        coords = cv.findNonZero(res)
        return coords.reshape((-1, 2)).tolist()
    
    coords_tl = find_corners(im, kernel) # corner 1
    coords_tr = find_corners(im, np.rot90(kernel, -1)) # corner 2
    coords_br = find_corners(im, np.rot90(kernel, -2)) # corner 3
    coords_bl = find_corners(im, np.rot90(kernel, -3)) # corner 4
    

    第一个变体/替代方案:

    # finding nearest corners in a certain direction
    # dx and dy either lock that axis down or allow candidates in that direction
    
    def closest_in_direction(x, y, dx, dy, coords):
        candidates = [
            i for i, (xx, yy) in enumerate(coords)
            if (((xx - x) * dx >= 0) if dx != 0 else (xx == x))
            and (((yy - y) * dy >= 0) if dy != 0 else (yy == y))
        ]
    
        if not candidates:
            return None
    
        def distance_predicate(i):
            xx, yy = coords[i]
            return max(abs(xx - x), abs(yy - y))
    
        return min(candidates, key=distance_predicate)
    
    # assembling boxes from corners
    
    boxes = []
    while len(coords_tl) > 0:
        x0, y0 = coords_tl.pop()
        x1 = y1 = None
        # looking for x1, y1 which is corner 3, diagonal to corner 1
    
        if (i2 := closest_in_direction(x0, y0, dx=1, dy=0, coords=coords_tr)) is not None:
            x1, y0_ = coords_tr.pop(i2)
            assert y0_ == y0
    
        if (i4 := closest_in_direction(x0, y0, dx=0, dy=1, coords=coords_bl)) is not None:
            x0_, y1 = coords_bl.pop(i4)
            assert x0_ == x0
    
        if (x1 is not None) and (y1 is not None):
            if (i3 := closest_in_direction(x1, y1, dx=0, dy=0, coords=coords_br)) is not None:
                coords_br.pop(i3)
            else:
                print("BR corner not found, inferred")
    
            boxes.append((x0, y0, x1, y1))
    
    assert len(coords_tl) == 0
    assert len(coords_tr) == 0
    assert len(coords_br) == 0
    assert len(coords_bl) == 0
    
    # draw the boxes
    
    pad = 5
    canvas = cv.cvtColor(im, cv.COLOR_GRAY2BGR)
    for box in boxes:
        (x0, y0, x1, y1) = box
        (x0, y0, x1, y1) = (x0 - pad, y0 - pad, x1 + pad, y1 + pad)
        cv.rectangle(canvas, (x0, y0), (x1, y1), color=(0, 255, 0))
    

    result


    这是 构建图形并提取连通分量的 第二种

    构建图表:

    • 有一个节点列表
    • 考虑所有对
    • 检查一对之间是否应该有边,

    寻找连通分量:

    • 选择任何 未访问的 节点(它们最初都是未访问的)
    • 找到它的邻居,找到他们的邻居,...直到找到所有邻居
    • 将其标记为已访问
    • 重复
    # (x, y, dx, dy) # dx,dy suggest edges in specific direction
    nodes = \
        [ (x,y, +1, +1) for (x,y) in coords_tl ] + \
        [ (x,y, -1, +1) for (x,y) in coords_tr ] + \
        [ (x,y, -1, -1) for (x,y) in coords_br ] + \
        [ (x,y, +1, -1) for (x,y) in coords_bl ]
    
    def construct_edges(nodes):
        edges = set()
        # run through all pairings, test for edgability, maybe create edge
        for i in range(len(nodes)-1):
            for j in range(i+1, len(nodes)):
                (ax, ay, adx, ady) = nodes[i]
                (bx, by, bdx, bdy) = nodes[j]
                dx = ax - bx
                dy = ay - by
    
                if ((ay == by) and (adx * bdx < 0) and (dx * adx < 0)) \
                or ((ax == bx) and (ady * bdy < 0) and (dy * ady < 0)):
                    edges.add((i, j))
    
        return edges
    
    edges = construct_edges(nodes)
    

    所有这些以任意顺序绘制的单边, 看起来 像是在正确的位置上画出了矩形……但实际上并不存在真正的矩形。这只是矩形的边缘。

    def get_connected_components(nodes, edges):
        components = set()
        # set of sets, theoretically, frozenset etc...
        # since our components are supposed to be contours, I'll give them order
        # they'll be ordered TL, TR, BR, BL. nodes are already ordered in this way.
    
        visited = np.zeros(len(nodes), dtype=bool)
    
        while not visited.all():
            i = np.argmin(visited) # find an unvisited node (min: false < true)
            component = {i}
            visited[i] = True
            # add transitively until all neighbors are visited.
            iterate = True # (component was extended, potentially more neighbors to visit)
            while iterate:
                iterate = False
                for u,v in edges:
                    if (u in component) ^ (v in component):
                        component |= {u, v}
                        visited[u] = True
                        visited[v] = True
                        iterate = True
    
            components.add(tuple(sorted(component))) # a set, theoretically, but eh
    
        return components
    
    components = get_connected_components(nodes, edges)
    

    其中每个都是节点索引列表。只需查找节点并收集它们的坐标即可。如果您确定所有框都有四个角,那么您可以按顺序绘制边。如果您不确定,请获取 X 和 Y 方向上的最小和最大坐标,并将其用于矩形。

    for component in components:
        coords = np.array([nodes[k][:2] for k in component])
        x0 = coords[:,0].min()
        x1 = coords[:,0].max()
        y0 = coords[:,1].min()
        y1 = coords[:,1].max()
    
        cv.rectangle(
            img=canvas,
            pt1=(x0 - pad, y0 - pad),
            pt2=(x1 + pad, y1 + pad),
            color=(0, 255, 0),
        )
    

    result

    (看起来是一样的,除了节点到节点的线之外,应该是一样)

  • 我正在做一个项目,用于检测前额的 Omega 状皱纹(抑郁症的诊断特征)。根据这篇论文,我发现 Hessian 过滤器是检测皱纹的好方法:Yap,M....

    我正在开展一个项目,用于检测前额的 Omega 状皱纹(抑郁症的诊断特征)。根据这篇论文,我发现 Hessian 滤波器是检测皱纹的好方法:

    Yap, MH、Alarifi, J.、Ng, C.、Batool, N. 和 Walker, K. (2019)。自动面部皱纹注释器。计算机科学讲义(第 676-680 页)。https: https://doi.org/10.1007/978-3-030-11018-5_5

    我使用了 skimage 的 hessian 过滤器,尽管根据 这个 ,但对我来说已经足够了。

    我按顺序应用了以下内容:

    1. 高斯模糊。
    2. Hessian 滤波器。
    3. 合拢操作。

    这是我的代码:

    image = io.imread("1.jpg", as_gray=True)
    image = cv2.GaussianBlur(image, (5,5), 0)
    hessian_image = filters.hessian(image)
    
    kernel = np.ones((5,5), np.uint8)
    closing = cv2.morphologyEx(hessian_image, cv2.MORPH_CLOSE, kernel)
    
    closing = np.array(np.divide(closing, np.amax(closing)), dtype=np.float64)
    closing *= 255
    closing = np.uint8(closing)
    
    cv2.imshow("Closing", closing)
    cv2.waitKey(0)
    

    这是输入图像:

    enter image description here

    这是输出图像:

    enter image description here

    我无法使用模板匹配检测类似 Omega(或类似矩形)的形状,因为它们在不同图像之间会有所不同。您还有其他想法吗?

  • brk 2月前 0 只看Ta
    引用 9

    我认识的抑郁症患者中没有一个人有这样的皱纹。这只限于 90 岁以上的人吗?只是好奇。在我看来,通过面部表情来诊断人是非常不道德的。

  • 引用 10

    也许你可以尝试使用连通分量来分析每个模式的结构。像这样:

    from skimage import measure, filters
    import numpy as np
    
    # segments by connected components
    segmentation = measure.label(closing)
    
    # finds areas of each connected component, omits background
    areas = np.bincount(segmentation.ravel())[1:]
    # finds threhsold to remove small components
    t = filters.threshold_otsu(areas)
    # finds connected component indexes, which areas greater than the threshold
    indexes = np.where(areas > t)[0] + 1
    # filters out small components
    dominant_patterns = np.isin(segmentation, indexes) 
    
    # this is applicable if the image well centered
    # forehead center is approximately positioned at image center
    # flip image by columns
    dominant_patterns_flipped = np.flip(dominant_patterns, axis=1)
    # finds intersection with flipped image
    omega_like = dominant_patterns * dominant_patterns_flipped
    

    注意:如果图像居中,即前额位于图像的中心,则适用此方法,并且假设存在垂直对称。

    这将给你以下图像:

    enter image description here

    现在我们可以按行和列对图像进行分析,以计算每行和每列的像素出现次数,使用以下函数:

    import numpy as np
    
    def row_col_profiles(image):
        """
        Returns pixels occurances per row and column
        """
        rows, cols = np.indices((image.shape))
        
        row_lengths = np.bincount(rows.ravel())
        number_of_pixels_on_each_row = np.bincount(rows.ravel(), image.ravel())
        row_profile = number_of_pixels_on_each_row / row_lengths
    
        col_lengths = np.bincount(cols.ravel())
        number_of_pixels_on_each_col = np.bincount(cols.ravel(), image.ravel())
        col_profile = number_of_pixels_on_each_col / col_lengths
    
        return row_profile, col_profile
    
    row_profile, col_profile = row_col_profiles(omega_like)
    

    您可以绘制如下轮廓图:

    import matplotlib.pyplot as plt
    
    fig, axes = plt.subplots(1,2, figsize=(10, 5))
    axes[0].plot(row_profile)
    axes[0].set_title("Horizontal Line Profile")
    axes[0].set_xlabel("Row Index")
    axes[0].set_ylabel("Bright Pixel Occurance")
    axes[0].grid()
    axes[1].plot(col_profile)
    axes[1].set_title("Vertical Line Profile")
    axes[1].set_xlabel("Column Index")
    axes[1].set_ylabel("Bright Pixel Occurance")
    axes[1].grid()
    

    你会得到类似这样的结果:

    为了检查我们是否有类似欧米茄的模式,我们可以从该配置文件中获取一些阈值,例如 0.2,并且检查至少我们在垂直配置文件中是否有 2 个相对相同水平的峰值(我使用了最大值的 -10%)。

    is_omega_like = row_profile.max()>=0.2 and col_profile.max()>=0.2 and len(np.where(col_profile>col_profile.max()*0.9)[0])>=2
    

    您还可以尝试测量一些属性,并在连通分量上找到一些合理的阈值。请查看 文档 .

  • 令人印象深刻!由于我是 skimage 的新手,您能为我推荐一些有用的属性吗?

  • 当然,您可以尝试 Hu 矩。它们是 7 个不变的尺度、旋转和平移特征。为此,您可以直接在每个单独的段上调用 ​​measure.moments_hu(segment),也可以在所有段上调用:measure.regionprops_table(segmentation, properties=('moments_hu', ))。从最后一个开始,您可以创建 pandas 数据框。似乎您没有标记数据,因此在计算所有图像和所有连接组件的 Hu 矩后,您可以尝试使用一些无监督学习将常见的数据聚类在一起。可能类似 omega 的模式会聚类在一起。

  • eb1 2月前 0 只看Ta
    引用 13

    我只有 4 张具有 Omega 类模式的图像,对于学习算法来说太少了。我可能会从数据增强中受益,但即使这样,我也没有 Omega 类测试数据来测试我的模型。

  • 引用 14

    在这种情况下,您可以尝试手动找到一些合理的阈值。我的意思是,您可以扫描您选择的特征并找到最佳阈值,这将 Omega 类与其他阈值区分开来。

返回
作者最近主题: