Opencv project combat: reconocimiento de hojas de respuestas de visión artificial

Elemento: reconocimiento de hoja de respuestas


dirección de github

dirección de github

El proceso de solución es el siguiente

Pretratamiento

Primero realice la detección de bordes Canny en la imagen y luego realice la operación de expansión. El propósito de la operación de expansión es, si el contorno exterior del papel no es muy obvio, después de la detección de bordes Canny, el
contorno exterior del papel es discontinuo y hay pequeños agujeros, use la operación de expansión para llenar los pequeños agujeros

Los resultados del procesamiento son los siguientes:

Inserte la descripción de la imagen aquí

Detección de contornos

Extraiga el contorno con el área más grande MaxContoury realice una aproximación de contorno adaptativa.epsilon = 0.0001 * 周长

El código específico es el siguiente:

#步长设置为周长的0.0001倍,一般来说取epsilon = 0.001倍周长
step = 0.0001 * cv2.arcLength(cnts[0], True)
epsilon = step

#不断递增epsilon直到近似所得轮廓正好包含四个点
while len(cnt) != 4:
    cnt = cv2.approxPolyDP(cnts[0], epsilon, True)

    #步增epsilon
    epsilon += step

Los resultados del procesamiento son los siguientes:

Inserte la descripción de la imagen aquí

Transformación de perspectiva

Antes de la transformación de perspectiva, se requiere un procesamiento previo avanzado y los cuatro puntos del contorno 左上、右上、右下、左下se ordenan en el orden, la parte de clasificación del código es la siguiente:

#将四个轮廓点排序
pts = np.zeros((4, 2), np.float32)

res = np.sum(points, axis=1)
pts[0] = points[np.argmin(res)]
pts[2] = points[np.argmax(res)]

res = np.diff(points, axis=1)
pts[1] = points[np.argmin(res)]
pts[3] = points[np.argmax(res)]

Luego encuentre el ancho máximo y la altura máxima, el código específico es el siguiente:

#计算边长
w1 = np.sqrt((pts[0][0] - pts[1][0]) ** 2 + (pts[0][1] - pts[1][1]) ** 2)
w2 = np.sqrt((pts[2][0] - pts[3][0]) ** 2 + (pts[2][1] - pts[3][1]) ** 2)
w = int(max(w1, w2))

h1 = np.sqrt((pts[1][0] - pts[2][0]) ** 2 + (pts[1][1] - pts[2][1]) ** 2)
h2 = np.sqrt((pts[0][0] - pts[3][0]) ** 2 + (pts[0][1] - pts[3][1]) ** 2)
h = int(max(h1, h2))

Después de todo el preprocesamiento, podemos comenzar nuestra última y más importante transformación de perspectiva escalonada, el código específico es el siguiente:

#目标四个点
dst = np.array([
    [0, 0],
    [w - 1, 0],
    [w - 1, h - 1],
    [0, h - 1]
], np.float32)

#透视变换
mat = cv2.getPerspectiveTransform(pts, dst)
paper1 = org1.copy()
paper1 = cv2.warpPerspective(paper1, mat, (w, h))
if show_process:
    imshow(paper1)

Los resultados son los siguientes:

Inserte la descripción de la imagen aquí

Pretratamiento

Después de obtener la imagen transformada en perspectiva, también es necesario realizar operaciones de preprocesamiento Primero, para eliminar la influencia de diferentes niveles de exposición de diferentes imágenes, la imagen debe ser primero una ecualización de histograma adaptativo.

Los resultados del procesamiento son los siguientes:

Inserte la descripción de la imagen aquí

Luego, la imagen se binariza para la detección de contornos. Pero la imagen de binarización terminada hay un problema, que se pinta la hoja de respuestas cuando, si no se pinta por completo,
puede causar una detección inexacta, por lo que para que los resultados sean una detección más precisa, pero también la necesidad de cerrar la operación de operación , Los resultados después del procesamiento son los siguientes:

Inserte la descripción de la imagen aquí

Detección de contorno + filtrado de contorno

Primero extraiga todos los contornos, y los resultados son los siguientes:

Inserte la descripción de la imagen aquí

Puede ver que se han extraído muchos contornos, muchos de los cuales son contornos que no necesitamos, por lo que necesitamos usar algunos algoritmos de filtrado para mantener los contornos (25 elipses) que necesitamos.
Los pasos del algoritmo de filtrado aquí son los siguientes:

  • Primero obtenga la figura circunscrita del contorno a detectar, si es un círculo, obtenga el círculo circunscrito del contorno
  • Luego se puede filtrar por área, cuando la relación área del perfil / área del patrón externo ratiosatisfaga: ratio > 0.8 and ratio < 1.2tiempo para cumplir con los requisitos
  • Luego se puede filtrar de acuerdo con la circunferencia, cuando se ratiosatisfaga la relación contorno de circunferencia / perímetro de los gráficos externos : ratio > 0.8 and ratio < 1.2cuando se cumplan los requisitos

El código específico es más complicado, como sigue:

#用于保存保留下来的轮廓
cntsex = []

#上下边界阈值
thresh_lower = 0.8
thresh_upper = 1.2

eps = 1e-6

show = org1.copy()

for cnt in cnts:
    
    cntcopy = cnt.copy()

    #按照h方向坐标对轮廓的所有点排序,找到最大的y
    cntcopy = sorted(cntcopy, key=lambda x: x[0][1], reverse=True)
    maxy = cntcopy[0][0][1]

    #按照w方向坐标对轮廓的所有点排序,找到最大的x
    cntcopy = sorted(cntcopy, key=lambda x: x[0][0], reverse=True)
    maxx = cntcopy[0][0][0]

    #获得椭圆的中心
    (x, y), radius = cv2.minEnclosingCircle(cnt)
    center = (int(x), int(y))
    radius = int(radius)

    #获得椭圆的长轴和短轴
    a = maxx - x
    b = maxy - y

    if b == 0:
        continue

    ratio = a / b;
    if ratio > 2 or ratio < 0.5:
        continue

    if radius == 0:
        continue
    
    #面积过滤
    areaex = np.pi * a * b
    area   = cv2.contourArea(cnt)

    ratio = area / areaex

    if ratio < thresh_upper and ratio > thresh_lower:
        cntsex.append(cnt)

    show = cv2.drawContours(show, [cnt], 0, (0, 255, 0), 1)
    show = cv2.ellipse(show, center, (int(a), int(b)), 0, 0, 360, (0, 0, 255), 1)

Después de esto tenemos todos los contornos que son más como elipses, pero esto no es suficiente, porque también se han conservado algunas elipses utilizadas para encuadernación. Se puede observar que
la característica de estas elipses utilizadas para encuadernación es que su área es mejor que la respuesta. La elipse de es mucho más pequeña, por lo que ordenamos todos los contornos key = 轮廓的面积
y luego filtramos el área más pequeña a través de un algoritmo específico. El código específico es el siguiente:

#第二次过滤
cnts = []
maxarea = -1e6

for cnt in cntsex:
    area = cv2.contourArea(cnt)

    if area > maxarea:
        maxarea = area

maxgap = 0.5 * maxarea

cntsex = sorted(cntsex, key=lambda x: cv2.contourArea(x), reverse=True)

prvarea = cv2.contourArea(cntsex[0])
cnts.append(cntsex[0])

for i in range(1, len(cntsex)):
    if abs(prvarea - cv2.contourArea(cntsex[i])) > maxgap:
        break
    cnts.append(cntsex[i])

El resultado final del procesamiento es el siguiente:

Inserte la descripción de la imagen aquí

Ordenar + comprobar

Luego, debe ordenar los contornos de arriba a abajo y de izquierda a derecha. Este programa completa la detección mientras ordena. El código específico es el siguiente:

#对多个轮廓按照从上到下的顺序排序
cnts = sorted(cnts, key=lambda x: x[0][0][1])

rows = int(len(cnts) / 5)

TAB = ['A', 'B', 'C', 'D', 'E']
ANS = []

#检查每一行(即每一题)的答案
for i in range(rows):
    subcnts = cnts[i*5:(i+1)*5]
    subcnts = sorted(subcnts, key=lambda x: x[0][0][0])

    total = []

    for (j, cnt) in enumerate(subcnts):
        mask = np.zeros(paper1.shape, dtype=np.uint8)
        cv2.drawContours(mask, [cnt], -1, 255, -1) #-1表示填充

        mask = cv2.bitwise_and(paper1, paper1, mask=mask)
        total.append(cv2.countNonZero(mask))

    idx = np.argmax(np.array(total))
    ANS.append(TAB[idx])

print(ANS)

Los resultados del procesamiento son los siguientes:

Inserte la descripción de la imagen aquí

Supongo que te gusta

Origin blog.csdn.net/lwh1023443389/article/details/108564877
Recomendado
Clasificación