4

前言

当前有一个需求,在之前进行对进行发票设别标注的时候遇到的痛点,那就是需要用户下载指定环境,而且有很多小毛病,无疑是增加了工作量。在这个基础上,就决定自己在web进行图像标注,由web端进行画框进行标图进行保存四个坐标,根据之后再根据四个坐标进行图像切片。基于此就有了这个文章,本文将介绍如何使用高斯面积公式(也称为Shoelace公式)计算多边形的有向面积,并结合图像处理技术对图像进行旋转裁剪。

裁剪的图片

image.png

实现的效果

cjy10_20240512_81_crop_0.jpg

实现步骤

  • 计算多边形的有向面积:利用高斯面积公式计算多边形的有向面积。如果面积为负,说明顶点是按逆时针顺序排列的,需要进行调整以确保顺时针顺序。
  • 计算裁剪图像的宽度和高度:根据调整后的顶点坐标,计算旋转裁剪后图像的宽度和高度。
  • 执行旋转裁剪:使用计算得到的宽度和高度,对图像进行旋转裁剪。

计算多边形的有向面积

 d = 0.0
    # 使用高斯面积公式计算多边形的有向面积
    for index in range(-1, 3):
        d += -0.5 * (points[index + 1][1] + points[index][1]) * (
                points[index + 1][0] - points[index][0])
        # 如果面积为负,交换点的位置以确保顺时针顺序
    if d < 0:
        tmp = np.array(points)
        points[1], points[3] = tmp[3], tmp[1]

为什么要确保为顺时针,这是因为我们需要计算固定长和宽

顺时针的情况

image.png

逆时针的情况

image.png

高斯面积公式

  1. Shoelace公式(也称为高斯面积公式或鞋带公式)是一个用于计算二维平面上简单多边形面积的算法。这个公式的基本思想是将多边形的顶点视为平面上的点,并通过这些点的坐标来计算多边形的面积。

高斯面积公式

image.png

鞋带公式

image.png

长的很像鞋带
为什么叫做鞋带公式,这是因为在计算的过程很像鞋带一样缠绕着,

鞋带公式是这样子算的:

image.png

鞋带公式多边形面积计算

比如一个多边形(四角形),

P1(x1,y2)=(2,1),P2(x2,y2)=(3, 3),P3(x3,y3)=(5, 4),P4(x4,y4)=(4, 2)

image.png

可得面积:
image.png

为什么要加绝对值,这就是涉及到高斯面积公式的推理过程,结合这个多边形案例

image.png

梯形公式

image.png

计算P1到P2的面积

 (x1, y1) = (2, 1) (x2, y2) = (3, 3)
 2/(x2y1 - x1y2) + 2/(x2y2-x1y1) 
 area = 2

image.png

计算P2 到 P3梯形面积

 (x2, y2) = (3, 3) (x3, y3) = (5, 4)
 2/(x3y2 - x2y3) + 2/(x3y3-x2y2) 
 area = 7

image.png

计算P3 到 P4 梯形面积
这里发现当前的x4-x3 当x3大于x4时,计算的梯形面积为负数

 (x3, y3) = (5, 4) (x4, y4) = (4, 2)
 2/(x4y3 - x3y4) + 2/(x4y4-x3y3) 
 are = -3

image.png

计算x4y4 x1y1 梯形面积
这里发现当前的x1-x4 当x4大于x1时,计算的梯形面积为负数

 (x1, y1) = (2, 1) (x4, y4) = (4, 2)
 2/(x1y4 - x4y1) + 2/(x1y1-x4y4) 
 area = -3

image.png

最后相加可得高斯面积公式

image.png

image.png

根据面积相加可得

 area1 + area2 + area3 + area4 = area
 2 + 7 - 3 -3 = 3

使用勾股定理计算图像长和宽

    [0, 0],[4, 0],[4, 3], [0, 3]
    # 计算裁剪图像的宽度
    img_crop_width = int(
        max(
             # 勾股定理
            np.linalg.norm(points[0] - points[1]), # 距离 (0,0) 到 (4,0)
            np.linalg.norm(points[2] - points[3]))) # 距离 (4,3) 到 (0,3)
    # 计算裁剪图像的高度
    img_crop_height = int(
        max(
            np.linalg.norm(points[0] - points[3]),  # 距离 (0,0) 到 (0,3)
            np.linalg.norm(points[1] - points[2]))) # 距离 (4,0) 到 (4,3)
    pts_std = np.float32([[0, 0], [img_crop_width, 0],
                              [img_crop_width, img_crop_height],
                              [0, img_crop_height]])

手动计算

image.png

得到长和框、裁剪四个坐标

    img_crop_width: 4, img_crop_height: 3
    pts_std = [[0. 0.]  [4. 0.][4. 3.][0. 3.]]

执行旋转裁剪

    # 获取透视变换矩阵
        M = cv2.getPerspectiveTransform(points, pts_std)
        # 进行透视变换
        dst_img = cv2.warpPerspective(
            img,
            M, (img_crop_width, img_crop_height),
            borderMode=cv2.BORDER_REPLICATE,
            flags=cv2.INTER_CUBIC)
        # 获取变换后图像的高度和宽度
        dst_img_height, dst_img_width = dst_img.shape[0:2]
        # 如果高度和宽度的比例大于等于1.5,则旋转图像
        if dst_img_height * 1.0 / dst_img_width >= 1.5:
            dst_img = np.rot90(dst_img)

获取透视变换矩阵

M = cv2.getPerspectiveTransform(points, pts_std)
  • cv2.getPerspectiveTransform(points, pts_std) 计算并返回一个 3x3 的透视变换矩阵 M。
  • points 是原始图像中四个点的坐标。
  • pts_std 是目标图像中对应四个点的坐标。
  • 透视变换矩阵 M 用于将原始图像中的四个点映射到目标图像中的四个点。

    poinst = np.float32([[260, 100], [600, 100], [260, 400], [600, 400]])
    pts_std = np.float32([[0, 0], [600, 0], [0, 600], [600, 600]])
    

进行透视变换

dst_img = cv2.warpPerspective(
    img,
    M, (img_crop_width, img_crop_height),
    borderMode=cv2.BORDER_REPLICATE,
    flags=cv2.INTER_CUBIC)
  • cv2.warpPerspective ,它会根据你提供的变换矩阵,把图片中的某些部分拉伸、压缩或扭曲到你希望的位置。
  • img 是输入的原始图像。
  • M 是透视变换矩阵。
  • (img_crop_width, img_crop_height) 指定输出图像的大小。
  • borderMode=cv2.BORDER_REPLICATE 当变换过程中有些像素点移到图像边界外时,使用边缘的像素颜色填充这些位置。

示例完整代码

# 进行切片和旋转
def get_rotate_crop(img, points):
    d = 0.0
    # 使用高斯面积公式计算多边形的有向面积
    for index in range(-1, 3):
        d += -0.5 * (points[index + 1][1] + points[index][1]) * (
                points[index + 1][0] - points[index][0])
        # 如果面积为负,交换点的位置以确保顺时针顺序
    if d < 0:
        tmp = np.array(points)
        points[1], points[3] = tmp[3], tmp[1]

    try:
        # 计算裁剪图像的宽度
        img_crop_width = int(
            max(
                np.linalg.norm(points[0] - points[1]),
                np.linalg.norm(points[2] - points[3])))
        # 计算裁剪图像的高度
        img_crop_height = int(
            max(
                np.linalg.norm(points[0] - points[3]),
                np.linalg.norm(points[1] - points[2])))
        # 标注的目标坐标
        pts_std = np.float32([[0, 0], [img_crop_width, 0],
                              [img_crop_width, img_crop_height],
                              [0, img_crop_height]])
        # 获取透视变换矩阵
        M = cv2.getPerspectiveTransform(points, pts_std)
        # 进行透视变换
        dst_img = cv2.warpPerspective(
            img,
            M, (img_crop_width, img_crop_height),
            borderMode=cv2.BORDER_REPLICATE,
            flags=cv2.INTER_CUBIC)
        # 获取变换后图像的高度和宽度
        dst_img_height, dst_img_width = dst_img.shape[0:2]
        # 如果高度和宽度的比例大于等于1.5,则旋转图像
        if dst_img_height * 1.0 / dst_img_width >= 1.5:
            dst_img = np.rot90(dst_img)
        return dst_img
    except Exception as e:
        print(e)

参考文章

https://en.wikipedia.org/wiki/Shoelace_formula
https://waiterxiaoyy.github.io/2020/03/20/%E9%9E%8B%E5%B8%A6%...


kexb
474 声望15 粉丝