[Modelo DBNet]

modelo DBNet

1. Breve introducción

  DBNet es un algoritmo de detección de texto basado en la segmentación. El algoritmo introduce el módulo de Binarización diferenciable (Differentiable Binarization) en el modelo de segmentación, de modo que el modelo se puede binarizar a través del mapa de umbral adaptativo, y el mapa de umbral adaptativo puede calcular la pérdida. En el proceso de entrenamiento del modelo, juega el papel de optimización del efecto auxiliar. Después de la verificación, este esquema no solo mejora el efecto de la detección de texto, sino que también simplifica el proceso de procesamiento posterior. En comparación con otros modelos de detección de texto, DBNet tiene ventajas relativamente grandes en efecto y rendimiento, y actualmente es un algoritmo de detección de texto de uso común.

2. Estructura del modelo

inserte la descripción de la imagen aquíEl modelo de detección de texto DB se puede dividir en tres partes:

  • Red troncal, responsable de extraer las características de la imagen
  • Red FPN, pirámide de funciones, funciones mejoradas de estructura
  • Red principal, calcular el mapa de probabilidad del área de texto

1.Red troncal

  La parte Backbone de la red de detección de texto DB utiliza la red de clasificación de imágenes, y las redes ResNet50 y ResNet18 se utilizan en el documento. Aquí, el tamaño específico de la imagen de entrada se explica de la siguiente manera:
  la imagen de entrada [1, 3, 640, 640] ingresa a la red básica de Backbone, y el tamaño se cambia a la mitad del tamaño original después de un cálculo de convolución, y luego cuatro veces de reducción de resolución Los mapas de características de cuatro escalas se generan de la siguiente manera:

inserte la descripción de la imagen aquí

2.red FPN

  La estructura piramidal de características FPN es un método común para que las redes convolucionales extraigan de manera eficiente las características de cada dimensión en las imágenes.
  La entrada de la red FPN es la salida de la parte Backbone, y la altura y el ancho del mapa de características de salida después del cálculo FPN son 1/4 de la imagen original, es decir, [1, 256, 160, 160].
inserte la descripción de la imagen aquí


Mapa de características 1/32: [1, N, 20, 20] ===> convolución + sobremuestreo 8 veces ===> [1, 64, 160, 160] Mapa de características 1/16: [1, N, 40, 40] ===> más sobremuestreo doble del mapa de características 1/32 ===> nuevo mapa de características 1/16 ==> convolución + sobremuestreo 4 veces ===> [1, 64, 160, 160] característica
1/8 mapa: [1, N, 80, 80] ===> Agregue dos veces el muestreo superior del nuevo mapa de características 1/16 ===> Nuevo mapa de características 1/8 ===> Convolución + muestreo superior 2x ===> [1, 64, 160, 160]
Mapa de funciones de 1/4: [1, N, 160, 160] ===> aumento de muestreo doble del nuevo mapa de funciones de 1/8 == => nuevo mapa de funciones de 1/4 ===> convolución ===> [1, 64, 160, 160]
mapa de funciones de fusión: [1, 256, 160, 160] # combinar 1/4, 1/8, 1/16, 1/32 los mapas de funciones se fusionan por canal capa

3. Red principal

  Calcule mapas de probabilidad de región de texto, mapas de umbral de región de texto y mapas binarios de región de texto.
  La red Head realizará un muestreo superior sobre la base de las características de FPN, mapeará las características de FPN desde el tamaño original de 1/4 al tamaño de la imagen original y, finalmente, fusionará las tres imágenes generadas, y la salida es [1, 3, 640, 640]
inserte la descripción de la imagen aquí

3. Generación de etiquetas

   Cuando el algoritmo DB realiza el entrenamiento del modelo, necesita generar dos imágenes de acuerdo con el cuadro de etiqueta: mapa de probabilidad y mapa de umbral. El proceso de generación se muestra en la siguiente figura:
inserte la descripción de la imagen aquí
  La línea roja en la imagen es el cuadro de anotación del texto, y el conjunto de puntos del cuadro de anotación de texto se representa de la siguiente forma:
           G = { S k } k = 1 n G = \{S_k\}_{k= 1}^nGRAMO={ Sk}k = 1n, n indica el número de vértices.
  En la imagen del polígono, expanda el marco de anotación rojo una distancia para obtener un marco de polígono verde y reduzca la distancia para obtener un marco de polígono azul.
  En el documento, se usa la misma distancia para encoger y expandir el cuadro de la etiqueta, y la fórmula de cálculo es:
               D = A ( 1 − r 2 ) LD =\cfrac{A(1 - r^2)}{L}D=Lun ( 1r2 ), L representa el perímetro, A representa el área, r representa la relación de escala, normalmente r=0.4
  El perímetro L y el área A del contorno poligonal se calculan mediante la biblioteca Polygon.
  De acuerdo con la distancia calculada, expanda y reduzca el marco de la etiqueta y use el algoritmo Vatti para realizarlo. Consulte el enlace https://github.com/fonttools/pyclipper y el documento chino https://www.cnblogs.com /zhigu/ p/11943118.html , simplemente llame a la operación de interfaz de la biblioteca pyclipper en python, un ejemplo simple es el siguiente.

import cv2
import pyclipper
import numpy as np
from shapely.geometry import Polygon

def draw_img(subject, canvas, color=(255,0,0)):
    """作图函数"""
    for i in range(len(subject)):
        j = (i+1)%len(subject)
        cv2.line(canvas, subject[i], subject[j], color)

# 论文默认shrink值
r=0.4
# 假定标注框
subject = ((100, 100), (250, 100), (250, 200), (100, 200))
# 创建Polygon对象
polygon = Polygon(subject)
# 计算偏置distance
distance = polygon.area*(1-np.power(r, 2))/polygon.length
print(distance)
# 25.2

# 创建PyclipperOffset对象
padding = pyclipper.PyclipperOffset()
# 向ClipperOffset对象添加一个路径用来准备偏置
# padding.AddPath(subject, pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON)
# adding.AddPath(subject, pyclipper.JT_SQUARE, pyclipper.ET_CLOSEDPOLYGON)
padding.AddPath(subject, pyclipper.JT_MITER, pyclipper.ET_CLOSEDPOLYGON)

# polygon外扩
polygon_expand = padding.Execute(distance)[0]
polygon_expand = [tuple(l) for l in polygon_expand]
print(polygon_expand)
# [(75, 75), (275, 75), (275, 225), (75, 225)]
# polygon内缩
polygon_shrink = padding.Execute(-distance)[0]
polygon_shrink = [tuple(l) for l in polygon_shrink]
print(polygon_shrink)
# [(125, 125), (225, 125), (225, 175), (125, 175)]

# 作图
canvas = np.zeros((300,350,3), dtype=np.uint8)
# 原轮廓用红色线条展示
draw_img(subject, canvas, color=(0,0,255))
# 外扩轮廓用绿色线条展示
draw_img(polygon_expand, canvas, color=(0,255,0))
# 内缩轮廓用蓝色线条展示
draw_img(polygon_shrink, canvas, color=(255,0,0))

cv2.imshow("Canvas", canvas) 
cv2.waitKey(0)

  La imagen general del efecto es la siguiente: el cuadro rojo es el cuadro de la etiqueta, el cuadro verde es el efecto después de expandirse y el cuadro azul es el efecto después de reducirse.
inserte la descripción de la imagen aquí

0. Ejemplos

   Supongamos que el tamaño de la imagen es (35, 30, 3) y hay un cuadro de anotación de texto text_box: [[10,10], [25,10], [25,20], [10,20]] en la imagen , como se muestra en la figura a continuación, el cuadro rojo es el cuadro de anotación de texto.
   Este ejemplo explica brevemente la creación de mapas de probabilidad, mapas de umbral y mapas de binarización.
inserte la descripción de la imagen aquí

1. Etiqueta del mapa de probabilidad

  Utilice el método de contracción para obtener las etiquetas de mapa de probabilidad requeridas para el entrenamiento de algoritmos.
  Después de que el cuadro de la etiqueta se encoja, el valor de probabilidad del área de cobertura es 1 y el valor de probabilidad del resto del área es 0

# 创建概率图
h, w = 30, 35
probability_map = np.zeros((h, w), dtype=np.float32)
# 标注区域内缩 
# 经过distance的公式计算(D=2.52)和pyclipper库的内缩坐标处理
# text_box: [[10,10], [25,10], [25,20], [10,20]] ===> shrink_box: [[13,13], [22,13], [22,17], [13,17]]
# shrink_box为标注框经过内缩后的区域
shrink_box = [[13,13], [22,13], [22,17], [13,17]]
shrink_box = np.array(shrink_box).reshape(-1,2)
# 将概率图中的shrink区域赋值为1
cv2.fillPoly(probability_map, [shrink_box.astype(np.int32)], 1)

  La siguiente figura es un mapa de probabilidad, donde el valor de probabilidad del área de texto es 1 y el valor de probabilidad del área de fondo es 0
inserte la descripción de la imagen aquí

2. Etiqueta del mapa de umbrales

  En el mapa de umbral, se debe calcular la distancia desde cada posición hasta el cuadro de etiqueta, y cuanto más cercana sea la distancia, mayor será el umbral.
Los pasos básicos son los siguientes:

(1) Expandir el marco de la etiqueta

import pyclipper
import numpy as np
from shapely.geometry import Polygon

# 论文默认shrink值
r=0.4
# 标注框
subject = [[10,10],[25,10],[25,20],[10,20]]
# 创建Polygon对象
polygon = Polygon(subject)

# 计算偏置distance
distance = polygon.area*(1-np.power(r, 2))/polygon.length
print(distance)
# 2.52

# 创建PyclipperOffset对象
padding = pyclipper.PyclipperOffset()
# 向ClipperOffset对象添加一个路径用来准备偏置
padding.AddPath(subject, pyclipper.JT_MITER, pyclipper.ET_CLOSEDPOLYGON)

# polygon外扩
polygon_expand = padding.Execute(distance)[0]
polygon_expand = [tuple(l) for l in polygon_expand]
print(polygon_expand)
# [(7, 7), (28, 7), (28, 23), (7, 23)]

(2) Calcular la distancia

   Después de expandir el cuadro de etiqueta, el área de texto se expande y es necesario calcular la distancia desde cada punto del área hasta el cuadro de etiqueta. El cuadro de etiqueta se considera como cuatro segmentos de línea, y se calcula la distancia desde cada punto de posición a estos cuatro segmentos de línea, y el valor mínimo se toma como la distancia final. La distancia se calcula con la ayuda de dos fórmulas triangulares: la ley de los cosenos y la fórmula del área. El proceso de cálculo específico es el siguiente:

inserte la descripción de la imagen aquí
Dado el número infinito h:
{ S = 1 2 ⋅ c ⋅ h S = 1 2 ⋅ a ⋅ b ⋅ sin α ⇒ h = a ⋅ bc ⋅ sin α \begin{cases} S = \cfrac{1}; {2 }\cdot c \cdot h \\ S = \cfrac{1}{2}\cdot a \cdot b \cdot sin\alpha \end{cases} ⇒ h = \cfrac{a \cdot b}{c } \ cdot sin \ vacío S=21ChS=21abs en αh=Casegundos en α
Entre ellos, los valores de a, b y c se pueden calcular por la distancia de posición,sin α sin\alphas en α se calcula por la ley de los cosenos:
cos 2 α = a 2 + b 2 − c 2 2 ab ⇒ sin α = 1 − cos 2 α cos^2\alpha = \cfrac{a^2 + b^2 - c^2}{2ab} ⇒ sen\alpha = \sqrt{\smash[b]{1 - cos^2\alpha}}co s2 un=2 aba2+b2C2s en α=1co s2 un

(3) Normalización de distancia

   Después de calcular el valor de cada punto de ubicación, se realiza la normalización. Todos los valores del área se dividen por la distancia previamente calculada, y el valor limitado está entre [0, 1]. Obtenga el valor de la escala de distancia relativa, como se muestra en la siguiente figura.
inserte la descripción de la imagen aquí

(4) Calcular el mapa de umbral

   Reste el valor normalizado de distancia de 1 para obtener el mapa de umbral. Cerca de la posición del cuadro de etiqueta, el umbral está cerca de 1 y el mapa de umbral se muestra a continuación.
inserte la descripción de la imagen aquíConsulte el código central de la siguiente manera, consulte paddleocr para obtener más detalles.

import cv2
import numpy as np
import pyclipper
from shapely.geometry import Polygon
from matplotlib import pyplot as plt 


class MakeBorderMap(object):
    def __init__(self,
                 shrink_ratio=0.4,
                 thresh_min=0.3,
                 thresh_max=0.7,
                 **kwargs):
        self.shrink_ratio = shrink_ratio
        self.thresh_min = thresh_min
        self.thresh_max = thresh_max

    def __call__(self, data):

        img = data['image']
        text_polys = data['polys']
        ignore_tags = data['ignore_tags']

        canvas = np.zeros(img.shape[:2], dtype=np.float32)
        mask = np.zeros(img.shape[:2], dtype=np.float32)

        for i in range(len(text_polys)):
            if ignore_tags[i]:
                continue
            self.draw_border_map(text_polys[i], canvas, mask=mask,data=data)
        #canvas = canvas * (self.thresh_max - self.thresh_min) + self.thresh_min

        data['threshold_map'] = canvas
        data['threshold_mask'] = mask
        return data

    def draw_border_map(self, polygon, canvas, mask, data):
        polygon = np.array(polygon)
        assert polygon.ndim == 2
        assert polygon.shape[1] == 2

        polygon_shape = Polygon(polygon)
        if polygon_shape.area <= 0:
            return
        distance = polygon_shape.area * (
            1 - np.power(self.shrink_ratio, 2)) / polygon_shape.length
        subject = [tuple(l) for l in polygon]
        padding = pyclipper.PyclipperOffset()
        #padding.AddPath(subject, pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON)
        padding.AddPath(subject, pyclipper.JT_MITER, pyclipper.ET_CLOSEDPOLYGON)

        padded_polygon = np.array(padding.Execute(distance)[0])
        cv2.fillPoly(mask, [padded_polygon.astype(np.int32)], 1.0)

        xmin = padded_polygon[:, 0].min()
        xmax = padded_polygon[:, 0].max()
        ymin = padded_polygon[:, 1].min()
        ymax = padded_polygon[:, 1].max()
        width = xmax - xmin + 1
        height = ymax - ymin + 1

        polygon[:, 0] = polygon[:, 0] - xmin
        polygon[:, 1] = polygon[:, 1] - ymin

        xs = np.broadcast_to(
            np.linspace(
                0, width - 1, num=width).reshape(1, width), (height, width))
        ys = np.broadcast_to(
            np.linspace(
                0, height - 1, num=height).reshape(height, 1), (height, width))

        distance_map = np.zeros(
            (polygon.shape[0], height, width), dtype=np.float32)
        for i in range(polygon.shape[0]):
            j = (i + 1) % polygon.shape[0]
            absolute_distance = self._distance(xs, ys, polygon[i], polygon[j])
            distance_map[i] = np.clip(absolute_distance / distance, 0, 1)
        distance_map = distance_map.min(axis=0)
        distance_map = np.round(distance_map, 3)
        data['distance_map'] = distance_map

        xmin_valid = min(max(0, xmin), canvas.shape[1] - 1)
        xmax_valid = min(max(0, xmax), canvas.shape[1] - 1)
        ymin_valid = min(max(0, ymin), canvas.shape[0] - 1)
        ymax_valid = min(max(0, ymax), canvas.shape[0] - 1)
        canvas[ymin_valid:ymax_valid + 1, xmin_valid:xmax_valid + 1] = np.fmax(
            1 - distance_map[ymin_valid - ymin:ymax_valid - ymax + height,
                             xmin_valid - xmin:xmax_valid - xmax + width],
            canvas[ymin_valid:ymax_valid + 1, xmin_valid:xmax_valid + 1])

    def _distance(self, xs, ys, point_1, point_2):
        '''
        compute the distance from point to a line
        ys: coordinates in the first axis
        xs: coordinates in the second axis
        point_1, point_2: (x, y), the end of the line
        '''
        height, width = xs.shape[:2]
        square_distance_1 = np.square(xs - point_1[0]) + np.square(ys - point_1[1])
        square_distance_2 = np.square(xs - point_2[0]) + np.square(ys - point_2[1])
        square_distance = np.square(point_1[0] - point_2[0]) + np.square(point_1[1] - point_2[1])

        cosin = (square_distance - square_distance_1 - square_distance_2) / (
            2 * np.sqrt(square_distance_1 * square_distance_2))
        square_sin = 1 - np.square(cosin)
        square_sin = np.nan_to_num(square_sin)
        result = np.sqrt(square_distance_1 * square_distance_2 * square_sin / square_distance)

        result[cosin < 0] = np.sqrt(np.fmin(square_distance_1, square_distance_2))[cosin< 0]
        return result

if __name__ == "__main__":
    data = dict()
    data['image'] = np.zeros((30, 35, 1), dtype=np.uint8)
    data['polys'] = [[[10,10],[25,10],[25,20],[10,20]]]
    data['ignore_tags'] = [False] 
    # 1. 声名MakeBorderMap函数
    generate_text_border = MakeBorderMap()
    
    # 2. 根据解码后的输入数据计算border
    data = generate_text_border(data)
    threshold_map = data['threshold_map']
    
    # 3. 阈值图可视化 
    plt.imshow(threshold_map)

inserte la descripción de la imagen aquí

3. Etiqueta de imagen binarizada

3. Cálculo de pérdidas

1. Función de pérdida BCELoss

   DBNet utiliza una función de pérdida de entropía cruzada binaria (entropía cruzada binaria) y es de etiqueta única, es decir, una muestra de entrada corresponde a una salida de clasificación (1 o 0). Para los datos D(x, y) que contienen N muestras, la fórmula de cálculo de la pérdida BCE es la siguiente:
pérdida = 1 N ∑ 1 ≤ i ≤ nli pérdida =\frac{1}{N} \sum_{\mathclap{1\ le i \ le n}} l_ipérdida _=norte11 yo norteyoyo
其中,li = − w ( yi ⋅ log ⁡ xi + ( 1 − yi ) ∗ log ⁡ ( 1 − xi ) ) l_i = -w(y_i\cdot \log x_i + (1-y_i)*\log(1- x_i))yoyo=w ( yyoiniciar sesiónXyo+( 1yyo)lo g ( 1Xyo)) es la pérdida correspondiente a la i-ésima muestra. woww es un hiperparámetro, para la clasificación binaria de una sola etiqueta, establezcawww no tiene efecto. La pérdida de la i-ésima muestra es:
li = { − log ⁡ xi if y = 1 − log ⁡ ( 1 − xi ) if y = 0 l_i = \begin{cases} -\log x_i &\text{if } & y =1 \\ -\log(1-x_i) &\text{if } & y=0 \end{casos}yoyo={ iniciar sesiónXyolo g ( 1Xyo)si si y=1y=0

2. Función de binarización

(1) Binarización estándar

  La red de segmentación semántica genera un mapa de probabilidad P ∈ RH ∗ WP\in R^{H*W}PAGRH W , dondeHHHwaWW __W representa alto y ancho, respectivamente. El mapa de probabilidad debe convertirse en un mapa binario, donde el valor de píxel es 1 para representar el área de texto. El proceso de binarización estándar es el siguiente:
B i , j = { 1 si P i , j > = t 0 si no es así B_{i,j} = \begin{cases} 1 &\text{if } &P_{i,j } >=t \\ 0 &\text{si} &de lo contrario \end{casos}Byo , j={ 10si si PAGyo , j>=tde otro modo _ _ _ _
Entre ellos, ttt es un umbral fijo,( i , j ) (i,j)( yo ,j ) representa las coordenadas en el gráfico.

(2) Binarización diferenciable

  La función de binarización estándar es discontinua, su proceso no es diferenciable y no se puede optimizar a medida que se entrena la red de segmentación semántica. Para resolver este problema, el trabajo propone una función escalonada que aproxima el proceso de binarización, es decir, Binarización diferenciable, el proceso es el siguiente: B ˆ i , j = 1 1 + e − k ( P i , j −
T i , j ) \^{B}_{i,j} = \frac{1}{1+e^{-k(P_{i,j}-T_{i,j})}}Bˆyo , j=1+mi- k ( PAGSyo , jTyo , j)1
donde B ˆ \^{B}Bˆ es el mapa binario de salida,TTT es el mapa de umbral adaptativo aprendido por la red,kkk representa el factor de aumento, generalmente establecido en 50.
En la figura (a) a continuación, SB representa el proceso de binarización estándar y DB representa el proceso de binarización diferenciable; la figura (b) y la figura © representan respectivamentel + l_+yo+y l - l_-yoLa razón del efecto de impulso de la curva derivada

  de DBNet puede explicarse por la propagación de gradiente inverso. En BCELoss, define f ( x ) = 1 1 + e − kxf(x)=\frac{1}{1+e^{-kx}}f ( x )=1 + mik x1, donde x = PAGS yo , j − T yo , jx=P_{i,j}-T_{i,j}X=PAGyo , jTyo , j. Luego, la pérdida de muestra positiva y la pérdida de muestra negativa se calculan de la siguiente manera:
{ l + = − log ⁡ 1 1 + e − kxl − = − log ⁡ ( 1 − 1 1 + e − kx ) \begin{cases} l_+ = - \log \frac{1}{1+e^{-kx}} \\ l_- = - \log (1- \frac{1}{1+e^{-kx}}) \end{casos}{ yo+=iniciar sesión1 + mik x1yo=lo g ( 11 + mik x1)
La derivada parcial de la pérdida con respecto a x se calcula de la siguiente manera:
{ ∂ l + ∂ x = − kf ( x ) e − kx ∂ l − ∂ x = kf ( x ) \begin{cases} \frac{\partial l_ +}{\parcial x} = -kf(x)e^{-kx} \\ \frac{\parcial l_-}{\parcial x} = kf(x) \end{casos}{ x∂l _+=- k F ( X ) mik xx∂l _=k f ( x )
Por derivadas parciales podemos notar que:

(1) El gradiente de la predicción errónea pasa por el factor de mejora kkk se fortalece, lo que conduce más al aprendizaje óptimo de la red y hace que los resultados de la predicción sean más claros
(2) La figura (b) esl + l_+yo+La curva derivada de , si se producen falsos positivos (las muestras positivas se predicen como muestras negativas, es decir, x<0), el valor de la derivada parcial inferior a 0 en la figura (b) es muy grande, lo que indica que la pérdida también es muy grande , y el gradiente puede ser más claro Volver
(3) La figura © es l − l_-yoLa curva derivada de , si se producen falsos positivos (las muestras negativas se predicen como muestras positivas, es decir, x>0), el gradiente también es relativamente grande y la pérdida también es grande

3. Cálculo de pérdidas totales

  Se generan tres mapas durante el entrenamiento del modelo: mapa de probabilidad, mapa de umbral y mapa de binarización. Por lo tanto, al calcular la función de pérdida, también es necesario combinar estos 3 gráficos y sus etiquetas reales correspondientes para construir una función de pérdida de 3 partes. La fórmula general de la función de pérdida se define de la siguiente manera:
L = L s + ɑ × L b + β × L t L=L_s + ɑ×L_b + β×L_tL=Ls+ɑ×Lsegundo+b×Lt
Entre ellos, L es la pérdida total, L s L_sLses la pérdida del mapa de probabilidad, L b L_bLsegundoes la pérdida de imagen binaria, L t L_tLtes la pérdida del mapa de umbral. ɑ ɑɑβ bβ es el coeficiente de peso, que se establece en 1 y 10 respectivamente en el documento.
L s = L segundo = ∑ yo ∈ S lyi Iniciar sesión ⁡ xi + ( 1 − yi ) Iniciar sesión ⁡ ( 1 − xi ) L_s = L_b = \sum_{\mathclap{i \in S_l}} y_i \log x_i + (1 -y_i)\log(1-x_i)Ls=Lsegundo=yo Syoyyoiniciar sesiónXyo+( 1yyo)lo g ( 1Xyo)
  paraL s L_sLsy L b L_bLsegundoTodos los cálculos de pérdidas utilizan BCELoss. Para resolver el problema de las muestras positivas y negativas desequilibradas, se utiliza una estrategia de conjunto de preguntas incorrecta en el proceso de cálculo de pérdidas, y la proporción de muestras positivas y negativas se establece en 1:3.
  l t l_tLtEl método de cálculo es expandir el polígono G d G_dGRAMOreLa suma de las distancias L1 del resultado de la predicción interna y la etiqueta del mapa de umbral.
L t = ∑ yo ∈ R re ∣ yi ∗ − xi ∗ ∣ L_t = \sum_{\mathclap{i \in R_d}} |{y_i}^* - {x_i}^*|Lt=yo RreyyoXyo
donde,R d R_dRrees el polígono extendido G d G_dGRAMOreÍndice de píxeles dentro de y ∗ y^*y es la etiqueta del mapa de umbral.

Supongo que te gusta

Origin blog.csdn.net/yewumeng123/article/details/127503815
Recomendado
Clasificación