numpy 二值图像中正方形的性质

eqfvzcg8  于 2023-10-19  发布在  其他
关注(0)|答案(1)|浏览(76)

我的任务是在二值图像中找到正方形的中心,边长和旋转Angular 。此外,我还提供了以下信息:给定一个白色(=True)背景的二进制图像,其中包含一个黑色(=False)旋转的正方形,返回所述正方形的质心(x,y),其边长(l)和其旋转Angular (alpha)。正方形将始终完全包含在图像中,即。它的任何边都不与图像边界相交。返回Angular alpha(以度为单位),其中0°是直立的,相当于...,-180°,-90°,90°,180°,...,旋转的正方向是逆时针。请注意img的形状是(高度,宽度),y轴优先(行优先顺序)。这个任务应该只能用标准的numpy函数来解决。
我对numpy不是很熟悉,但我想我可以用下面的代码得到x和y坐标中的正方形的中心和边长:

import numpy
def find_rect(img):
    x, y, l, alpha = 0, 0, 0, 0
    black_pixels = np.argwhere(img==0)
    y, x = np.mean(black_pixels, axis=0)
    l = np.sqrt((img==0).sum()) 
    y_max1 = black_pixels[black_pixels[:,1]>= x, 0].max()
    y_max2 = black_pixels[black_pixels[:,1]<= x, 0].max()
    y_max = max(y_max1, y_max2)
    x_max = black_pixels[black_pixels[:, 0] == y_max, 1][0]
    alpha = np.degrees(np.arctan(((y_max - y)/(x_max - x))))
    return x, y, l, alpha

#the code which generates the square
from scipy.ndimage import rotate
def generate_rot_square(w, h, x, y, l, alpha):
    rect = np.ones((l, l))
    rect = rotate(rect, alpha)
    r_h, r_w = rect.shape
    r_h_1 = r_h // 2
    r_h_2 = r_h - r_h_1
    r_w_1 = r_w // 2
    r_w_2 = r_w - r_w_1

    result = np.zeros((h, w), dtype=bool)
    result[y - r_h_1:y + r_h_2, x - r_w_1:x + r_w_2] = rect > 0.9
    return 1 - result

但是,一旦正方形被旋转,如果正方形不再起作用,代码就会得到边长。另外,我真的没有一个关于如何得到广场的旋转Angular 的想法。也许有人能给我给予如何完成这项任务。

4dc9hkyq

4dc9hkyq1#

这是相当学者,所以不切实际的(在现实生活中)解决方案可能是预期的。而不是计算机视觉解决方案(使用opencv而不仅仅是numpy)。
例如,既然你确定这是一个正方形,你就知道黑色像素的数量是l²。所以计算黑色像素的数量,并取其平方根

l=np.sqrt((img==0).sum())

为了求出Angular ,问问你自己,Angular α的ymax应该是多少?找到ymax,就像你做的那样,并从中推导出Angular 。
你必须能够找到Angular ,以区分实际情况,其中ymax发生在第一或第二四边形(或第三/第四,取决于y轴向上或向下。但只有两种情况需要考虑)
因此,计算图像的前半部分(x方向)的ymax 1,另一半的ymax 2

ymax1=black_pixels[black_pixels[:,1]>=x, 0].max()
ymax2=black_pixels[black_pixels[:,1]<=x, 0].max()

只有最大的一个是相关的。因为较小的一个意味着最大值不是一个角,而是一个边与y轴的交点(好吧,你也可以从这个交点计算,但这更复杂)。
所以,大一点的是角落。从角的位置和l可以推导出Angular 。
我试过了它不是很精确(对于接近90度的Angular ,误差在5度左右;对于较小的Angular 更好)。即使是我,也是149,当我试着用150的时候。这是因为>0.9过滤掉了比它应该过滤掉的更多的像素。
因此,如果>0.9是一个约束,那么避免依赖l来计算Angular 可能会更准确。为此,还计算x匹配ymax(警告:这不是一个xmax。只是画一个正方形,看看它是不一样的。它应该是行ymax中唯一的黑色像素:img[ymax].argmin()。或者,如果有多个黑色像素,则为该行中黑色像素的平均值(但是,除了Angular 0之外,应该只有几个)。那么ymax和x的比值应该与Angular 的tan有关。
我不多说了,因为你只是要小费。
但是,除了我提到的分离(x之前的max,x之后的max),或者角坐标(x匹配ymax,或者同样,冗余地,y匹配xmax)之外,你已经在做的黑色像素的计算(mean,max,min,.)都是你需要的。回想一下,如果一个正方形的圆心是x₀,y₀,边长是W,旋转Angular 是α,那么这些值会是什么。然后把等式反过来。
尝试不同的策略:

  • 就是从x前后的ymax和l中推导出Angular 。
  • 从ymax中找到匹配x(因此是一个角)并从中推导出tan(angle)
  • 同样,这两个策略都是用x代替y:
  • 在y之前计算xmax,在y之后计算xmax。从这个Angular 和l推导出
  • 找到与xmax匹配y(因此是一个角),并从中推导(也用arctan)Angular

也许你会看到,根据估计的Angular ,一个策略是更准确的,你最终会有一种方法来选择这4个策略中更好的。
还有其他的:ymax-ymin、xmax-xmin也取决于Angular 。所以如果你能告诉它们对于给定的Angular 应该是什么,那么你就可以颠倒公式,从它们的测量值中找到Angular 是什么

编辑

一些关于角落和Angular 的精确度。
在一个正方形里,你有四个角。它们的坐标
(x+W× π 2/2×cos(β),y+W× π 2/2×sin(β))
(x+W× π 2/2×cos(β+90°),y+W× π 2/2×sin(β+90°))
(x+W× π 2/2×cos(β+180°),y+W× π 2/2×sin(β+180°))
(x+W× π 2/2×cos(β+270°),y+W× π 2/2×sin(β+270°))
β开始α+45°,α是你要找的Angular 。对我来说,从角β进行推理更方便,因为角β是我们找到角的地方。所以如果正方形不旋转,α=0,那么β=45°。因此,角的坐标为(x+W ×cos(45°)=x+W× cos 2/2× cos 2/2 = x+W/2,y+W/2
x-W/2,y+W/2
x-W/2,y-W/2
x+W/2,y-W/2
如你所见,这是意料之中的。
如果α=45°,那么β=90°,我们得到x,y+W = 2/2
x-W 2/2,y
x,y-W 2/2
x+W 2/2,y
又一次,正如预期的那样。
我们使用哪个角落并不重要:用途:正如你所说,角模90°有效,显然,角之间由90°的角倍数分隔开
所以,我们只需要找个角落,随便。
如果你看ymax,它是一个角的y。除非角α=0,在这种情况下,它是整条边的y
什么是匹配x:它是x,其中你在行ymax中找到0。
例如,您可以通过这种方式找到它(使用black_pixels数组)

xc=black_pixels[black_pixels[:,0]==ymax,1][0]

请注意,black_pixels[black_pixels[:,0]==ymax,1]返回一个x数组。所以我只拿第一个(见后面)。
同样,直接处理图像,我也可以计算出

xc=img[ymax].argmin()

它返回ymax行中第一个0的索引(在x中,因为我们已经用ymax索引了第一个轴)。
如果该行中没有0,则这两种方法都将失败。但有一个。否则,ymax会有所不同。
所以,不,你知道一个角,它的坐标是(xc,ymax)。
该点是相对于具有向量(xc-x,ymax-y)的正方形的中心(和旋转中心)。

这个向量的Angular 是arctan((ymax-y)/(xc-x))。因为这是你找到一个角的Angular ,我把它叫做β,我们需要去掉45°。所以Angular 是arctan((ymax-y)/(xc-x))-45。至少如果我们正在工作的角落是在右边。
90-that,因为y轴向下。
只是玩不同的Angular ,看看如何arctan((ymax-y)/(xc-x))演变.

完整代码

def computeFeatures(img):
    # from your code
    black_pixels=np.argwhere(img==0)
    # number of black pixels
    n=len(black_pixels)
    # Hence, side of the square
    W=np.sqrt(n)
    # Center of the square
    y,x=black_pixels.mean(axis=0)
    # Let's now try to find the top corner
    # Since y axes is downward for an image (opencv rotate rotates
    # in trigonometric direction... if image is plot with y axis downward)
    # the top corner is ymin
    # (we could compute with ymax, as mentioned. Just then we have
    # to reason with bottom corner. It's a bit the same, with 
    # just a minus sign. But's it's easier to split explanation with ymin
    _,ymin=black_pixels.min(axis=0) # (I don't use xmin)
    # xc1 is the first x that is black in ymin row
    xc1=img[ymin].argmin()
    # xc2 is the last x that is black
    # Note that with angles around 45, the corner is quite "sharp"
    # so xc1 and xc2 are likely equal
    # but for "almost horizonal" (angle close to 0 or 90)
    # then, we have some "aliasing" and there are more than 1 pixel on
    # the last row 
    # not much, tho, so that is clearly polishing. 
    # Except for the caricatural case of angle 0. In which case
    # xc1 or xc2 would give the same result (just 2 different corners)
    xc2=img.shape[1]-1-img[ymin,::-1].argmin()

    if xc2>x:
        # If the corner is after x, that is on the right of the image
        # Then we prefer xc2 rather than xc1
        # (again, almost the same thing. But the corner is then the rightmost
        # point of the top row)
        # Then relative to center x,y, the vector center→top corner
        # is (xc2-x),(y-ymin),         
        # (with positive direction upward. So not exactly in
        # the coordinates of the image, but in the one of
        # a trigonometric circle 
        # So angle of top corner (in the trigonometric circle) is
        β=np.arctan((y-ymin)/(xc2-x))*180/np.pi # *180/pi convert in degs
        # So angle of rotation, since we expect the top corner
        # to be at 45° when rotation is 0, and, of course
        # to rotate then relatively with the rest of the square
        # is 45° less than angle of top corner
        α=β-45
    else:
        # Otherwise, we are dealing with a top corner that is on the 
        # left of the image
        # it is more or less the same thing.
        # Except that arctan (which is ambiguous as you know)
        # would give the angle of the opposite corner 
        # (btw, it is a valid way to express rotation. But it is
        # probably better to show rotation between 0 and 90°
        # we could have done that in a single computation
        # and just %90 the result. But it is easier when 
        # trying to understand to separate the 2 cases
        # Therefore, since we have not the top corner, but the
        # opposite one, we have to add 180°, and then remove 45°
        # like in the other case
        β=np.arctan((y-ymin)/(xc1-x))*180/np.pi
        α=β+180-45
    return x,y,W,α

“证明”它有效

import matplotlib.pyplot as plt
realAngles = np.arange(90)
estimatedAngles = [computeFeatures(generate_rot_square(1000,1000,500,500,700,a))[3] for a in realAngles]
plt.plot(realAngles, estimatedAngles)
plt.plot(realAngles, realAngles)
plt.show()

真实的和估计的Angular 重叠得很好。

相关问题