Python:使用 OpenCV 从左上角到右下角对项目进行排序

Posted

技术标签:

【中文标题】Python:使用 OpenCV 从左上角到右下角对项目进行排序【英文标题】:Python: Sorting items from top left to bottom right with OpenCV 【发布时间】:2021-06-30 22:45:08 【问题描述】:

如何尝试从左上角到右下角对图片的项目进行排序,如下图所示?目前收到此错误,代码如下。

错误:

a = sorted(keypoints, key=lambda p: (p[0]) + (p1))[0] # 找到左上角 ValueError:具有多个元素的数组的真值不明确。使用 a.any() 或 a.all()

本题仿照:Ordering coordinates from top left to bottom right

def preprocess(img):
    img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    img_blur = cv2.GaussianBlur(img_gray, (5, 5), 1)
    img_canny = cv2.Canny(img_blur, 50, 50)
    kernel = np.ones((3, 3))
    img_dilate = cv2.dilate(img_canny, kernel, iterations=2)
    img_erode = cv2.erode(img_dilate, kernel, iterations=1)
    return img_erode

image_final = preprocess(picture_example.png)
keypoints, hierarchy = cv2.findContours(image_final, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
points = []

while len(keypoints) > 0:
    a = sorted(keypoints, key=lambda p: (p[0]) + (p[1]))[0]  # find upper left point
    b = sorted(keypoints, key=lambda p: (p[0]) - (p[1]))[-1]  # find upper right point

    cv2.line(image_final, (int(a.pt[0]), int(a.pt[1])), (int(b.pt[0]), int(b.pt[1])), (255, 0, 0), 1)

    # convert opencv keypoint to numpy 3d point
    a = np.array([a.pt[0], a.pt[1], 0])
    b = np.array([b.pt[0], b.pt[1], 0])

    row_points = []
    remaining_points = []
    for k in keypoints:
        p = np.array([k.pt[0], k.pt[1], 0])
        d = k.size  # diameter of the keypoint (might be a theshold)
        dist = np.linalg.norm(np.cross(np.subtract(p, a), np.subtract(b, a))) / np.linalg.norm(b)   # distance between keypoint and line a->b
        if d/2 > dist:
            row_points.append(k)
        else:
            remaining_points.append(k)

    points.extend(sorted(row_points, key=lambda h: h.pt[0]))
    keypoints= remaining_points

新图片:

参考订购图片:

将使用质心来确定中心点排序。

【问题讨论】:

为什么会被否决?努力学习 底部的皱眉脸不容易被识别为一体! 【参考方案1】:

生成的编号取决于您希望有多少行。使用我将向您展示如何制作的程序,您可以在运行程序之前指定行数。

例如,这里是原图:

这是指定 4 行时的编号图像:

这是指定 6 行时的编号图像:

对于您提供的另一张图片(其框架被裁剪,因此该框架不会被检测为形状),您可以看到将有 4 行,因此将 4 放入程序将为您提供:


让我们看一下考虑到 4 行的工作流程。我使用的概念是将图像沿 y 轴分成 4 段,形成 4 行。对于图像的每个片段,找到每个以该片段为中心的形状。最后,按 x 坐标对每个段中的形状进行排序。

    导入必要的库:
import cv2
import numpy as np
    定义一个函数,该函数将接受图像输入并将处理后的图像返回到允许 python 稍后检索其轮廓的东西:
def process_img(img):
    img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    img_canny = cv2.Canny(img_gray, 100, 100)
    kernel = np.ones((2, 3))
    img_dilate = cv2.dilate(img_canny, kernel, iterations=1)
    img_erode = cv2.erode(img_dilate, kernel, iterations=1)
    return img_erode
    定义一个返回轮廓中心的函数:
def get_centeroid(cnt):
    length = len(cnt)
    sum_x = np.sum(cnt[..., 0])
    sum_y = np.sum(cnt[..., 1])
    return int(sum_x / length), int(sum_y / length)
    定义一个函数,该函数将接收处理后的图像并返回图像中找到的形状的中心点:
def get_centers(img):
    contours, hierarchies = cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
    for cnt in contours:
        if cv2.contourArea(cnt) > 100:
            yield get_centeroid(cnt)
    定义一个函数,该函数将接收图像数组img、坐标数组centers、图像的段数row_amt,以及每个段的高度row_h,作为输入。它将返回 row_amt 数组(按它们的 x 坐标排序),每个数组都包含 centers 中位于图像对应行中的每个点:
def get_rows(img, centers, row_amt, row_h):
    centers = np.array(centers)
    d = row_h / row_amt
    for i in range(row_amt):
        f = centers[:, 1] - d * i
        a = centers[(f < d) & (f > 0)]
        yield a[a.argsort(0)[:, 0]]
    读入图像,使用定义的processed函数获取其处理后的形式,并使用定义的centers函数获取图像中每个形状的中心:
img = cv2.imread("shapes.png")
img_processed = process_img(img)
centers = list(get_centers(img_processed))
    获取图像的高度以用于定义的get_rows 函数,并定义一个计数变量count 来跟踪编号:
h, w, c = img.shape
count = 0
    循环穿过分为 4 行的形状中心,绘制连接行的线以进行可视化:
for row in get_rows(img, centers, 4, h):
    cv2.polylines(img, [row], False, (255, 0, 255), 2)
    for x, y in row:
    添加到count 变量,并从row 数组中将count 绘制到图像上的特定位置:
        count += 1
        cv2.circle(img, (x, y), 10, (0, 0, 255), -1)  
        cv2.putText(img, str(count), (x - 10, y + 5), 1, cv2.FONT_HERSHEY_PLAIN, (0, 255, 255), 2)
    最后,显示图片:
cv2.imshow("Ordered", img)
cv2.waitKey(0)

总共:

import cv2
import numpy as np

def process_img(img):
    img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    img_canny = cv2.Canny(img_gray, 100, 100)
    kernel = np.ones((2, 3))
    img_dilate = cv2.dilate(img_canny, kernel, iterations=1)
    img_erode = cv2.erode(img_dilate, kernel, iterations=1)
    return img_erode
    
def get_centeroid(cnt):
    length = len(cnt)
    sum_x = np.sum(cnt[..., 0])
    sum_y = np.sum(cnt[..., 1])
    return int(sum_x / length), int(sum_y / length)

def get_centers(img):
    contours, hierarchies = cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
    for cnt in contours:
        if cv2.contourArea(cnt) > 100:
            yield get_centeroid(cnt)

def get_rows(img, centers, row_amt, row_h):
    centers = np.array(centers)
    d = row_h / row_amt
    for i in range(row_amt):
        f = centers[:, 1] - d * i
        a = centers[(f < d) & (f > 0)]
        yield a[a.argsort(0)[:, 0]]

img = cv2.imread("shapes.png")
img_processed = process_img(img)
centers = list(get_centers(img_processed))

h, w, c = img.shape
count = 0

for row in get_rows(img, centers, 4, h):
    cv2.polylines(img, [row], False, (255, 0, 255), 2)
    for x, y in row:
        count += 1
        cv2.circle(img, (x, y), 10, (0, 0, 255), -1)  
        cv2.putText(img, str(count), (x - 10, y + 5), 1, cv2.FONT_HERSHEY_PLAIN, (0, 255, 255), 2)

cv2.imshow("Ordered", img)
cv2.waitKey(0)

【讨论】:

谢谢,你能更深入地了解这三行吗? f = 中心[:, 1] - d * i a = 中心[(f 0)] 产生 a[a.argsort(0)[:, 0]] 我们想要找到centers 数组中位于i 行内的所有点。 f = centers[:, 1] - d * i 返回 centers 数组,其中每个 y 坐标减去图像顶部和 i 行顶部之间的距离。所以基本上它就像i 行向上移动直到它触及图像的顶部。使用移位的图像,我们可以简单地检查点是否位于图像顶部和行的高度内,因此a = centers[(f &lt; d) &amp; (f &gt; 0)] a.argsort(0) 返回 a 的索引,其中 x 坐标和 y 坐标已排序。由于我们只想按 x 坐标对行进行排序,因此我们使用切片 [:, 0],这意味着 0 列中的所有行。所以a.argsort(0)[:, 0] 是索引数组,yield a[a.argsort(0)[:, 0]] 产生按 0 列排序的行。我意识到a[a.argsort(0)[:, 0]]实际上可以替换为a[a[:, 0].argsort()]【参考方案2】:

这与您从中获取代码的链接问题中的任务不完全相同:

    你有轮廓,而另一个问题有要点。 你必须想出一种方法来对轮廓进行排序(它们可能在一维上重叠等等......)。有多种方法可以做到这一点,具体取决于您的用例。最简单的可能是使用轮廓的质心。这可以在这里完成:Center of mass in contour (Python, OpenCV)。然后,您可以从中创建一个对象数组,其中包含点并使用您找到的代码。 您找到的代码假定点基本上或多或少位于网格上。因此,参考图像上的所有点 1-5 大致在一条线上。在您发布的新图片中,情况并非如此。在这里采用聚类方法可能会更好:使用某种方法(可能是一个from here)对中心点 y 坐标进行聚类。然后对于每个集群:按中心 x 坐标对元素进行排序。

正如我已经说过的,有多种方法可以做到这一点,这几乎不取决于您的用例。

【讨论】:

以上是关于Python:使用 OpenCV 从左上角到右下角对项目进行排序的主要内容,如果未能解决你的问题,请参考以下文章

从左上角到右下角的像素积分图

坐标从左上到右下排序

在python中的openCV中绘制一个旋转的框

雕刻效果的实现OpenCV+QT

如何在图像时间序列中一致地对轮廓进行编号?

opencv找外接矩形,找的轮廓偏小,怎么解决