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

Python: Sorting items from top left to bottom right with OpenCV

提问人:mattsmith5 提问时间:4/5/2021 最后编辑:mattsmith5 更新时间:6/26/2022 访问量:4140

问:

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

错误:

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

这个问题是这样建模的:从左上角到右下角对坐标进行排序

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

新图片:

enter image description here

参考订购图片:

enter image description here

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

python-3.x opencv 图像处理

评论

0赞 Bilal 4/8/2021
底部皱眉的脸不容易被察为一个形状!

答:

1赞 S. Vogt 4/7/2021 #1

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

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

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

15赞 Red 4/9/2021 #2

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

例如,下面是原始图像:

enter image description here

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

enter image description here

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

enter image description here

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

enter image description here


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

  1. 导入必要的库:
import cv2
import numpy as np
  1. 定义一个函数,该函数将接收图像输入并将处理后的图像返回给允许 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
  1. 定义一个函数,该函数将返回轮廓的中心:
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)
  1. 定义一个函数,该函数将接收已处理的图像并返回图像中找到的形状的中心点:
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)
  1. 定义一个函数,该函数将接受图像数组、坐标数组、图像的线段数以及每个线段的高度 作为输入。它将返回数组(按其 x 坐标排序),每个数组都包含位于图像相应行中的每个点:imgcentersrow_amtrow_hrow_amtcenters
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]]
  1. 读取图像,使用定义的函数获取其处理后的形式,并使用定义的函数获取图像中每个形状的中心:processedcenters
img = cv2.imread("shapes.png")
img_processed = process_img(img)
centers = list(get_centers(img_processed))
  1. 获取要用于所定义函数的图像高度,并定义一个计数变量 ,以跟踪编号:get_rowscount
h, w, c = img.shape
count = 0
  1. 循环浏览分为 4 行的形状中心,绘制连接行的线以进行可视化:
for row in get_rows(img, centers, 4, h):
    cv2.polylines(img, [row], False, (255, 0, 255), 2)
    for x, y in row:
  1. 添加到变量中,然后从数组中绘制到图像上的特定位置:countcountrow
        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)
  1. 最后,显示图像:
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)

评论

0赞 mattsmith5 4/12/2021
谢谢,您能更深入地了解这三条线吗?f = centers[:, 1] - d * i a = centers[(f < d) & (f > 0)] 产生 a[a.argsort(0)[:, 0]]
3赞 Red 4/12/2021
我们想找到数组中位于行内的所有点。返回数组,其中每个 y 坐标减去图像顶部和行顶部之间的距离。所以基本上就像行向上移动,直到它触及图像的顶部。使用移动的图像,我们可以简单地检查点是否位于图像的顶部和行的高度内,因此 .centersif = centers[:, 1] - d * icentersiia = centers[(f < d) & (f > 0)]
2赞 Red 4/13/2021
retuurns a 的索引,其 x 坐标和 y 坐标排序。由于我们只想按其 x 坐标对行进行排序,因此我们使用 slice ,即列中的所有行。索引数组也是如此,yield 产生按 0 列排序的行。我意识到这实际上可以替换为a.argsort(0)[:, 0]0a.argsort(0)[:, 0]a[a.argsort(0)[:, 0]]a[a.argsort(0)[:, 0]]a[a[:, 0].argsort()]
0赞 Jeru Luke 6/28/2022
@AnnZen 对这个问题悬赏有什么具体原因吗?顺便说一句,你的答案看起来很棒 +1
1赞 Red 6/28/2022
@JeruLuke谢谢!是的。我只是提请大家更多地关注我发现高质量的旧答案。当然,我会在赏金结束时将赏金奖励给另一个伟大的答案。干杯!