Боевой алгоритм KNN: идентификация проверочного кода

алгоритм машинное обучение
Боевой алгоритм KNN: идентификация проверочного кода

Существует множество способов идентификации проверочного кода, например tesseract, SVM и т. д. Предыдущие статьи представилиАлгоритм KNN, Основное обучение сегодня — как использовать KNN для идентификации проверочного кода.

подготовка данных

В этом эксперименте в качестве упражнения используется проверочный код CSDN. Соответствующий интерфейс: https://download.csdn.net/index.php/rest/tools/validcode/source_ip_validate/10.5711163911089325.

В настоящее время существует два типа кодов подтверждения, возвращаемых интерфейсом:

  • Код проверки является чисто цифровым и мало вмешивается.После простого удаления фона, бинаризации и пороговой обработки изображения его можно идентифицировать с помощью алгоритма kNN.
  • Буквы плюс цифры, фоновые помехи и небольшая деформация позиций графических символов, после удаления фона, бинаризации и пороговой обработки картинки, используют алгоритм kNN для идентификации

Здесь выберите второе, чтобы взломать. Поскольку размеры изображений двух кодов подтверждения различны, вы можете использовать размер изображения, чтобы определить, какой из них является первым кодом подтверждения, а какой — вторым кодом подтверждения.

Скачать проверочный код

import requests
import uuid
from PIL import Image
import os
url = "http://download.csdn.net/index.php/rest/tools/validcode/source_ip_validate/10.5711163911089325"
for i in range(1000):
    resp = requests.get(url)
    filename = "./captchas/" + str(uuid.uuid4()) + ".png"
    with open(filename, 'wb') as f:
        for chunk in resp.iter_content(chunk_size=1024):
            if chunk:  # filter out keep-alive new chunks
                f.write(chunk)
                f.flush()
        f.close()
    im = Image.open(filename)
    if im.size != (70, 25):
        im.close()
        os.remove(filename)
    else:
        print(filename)

разделительный характер

После скачивания нужно разделить буквы. Разделение символов все еще немного хлопотно.

Оттенки серого

Преобразуйте цветное изображение в изображение в градациях серого для последующей обработки бинаризации, пример кода:

from PIL import Image
 
file = ".\\captchas\\0a4a22cd-f16b-4ae4-bc52-cdf4c081301d.png"
im = Image.open(file)
im_gray = im.convert('L')
im_gray.show()

Перед обработкой:

После обработки:

Бинаризация

После оттенков серого цветные пиксели имеют значение от 0 до 255. Бинаризация заключается в изменении пикселей, превышающих определенное значение, до 255 и изменении пикселей, меньших этого значения, до 0. Пример кода:

from PIL import Image
import numpy as np
file = ".\\captchas\\0a4a22cd-f16b-4ae4-bc52-cdf4c081301d.png"
im = Image.open(file)
im_gray = im.convert('L')
# im_gray.show()
 
pix = np.array(im_gray)
print(pix.shape)
print(pix)
 
threshold = 100 #阈值
 
pix = (pix > threshold) * 255
print(pix)
 
out = Image.fromarray(pix)
out.show()

Результат бинаризации на выходе:

удалить границу

По результатам вывода бинаризации мы видим, что помимо символов есть границы, и границы нужно убрать перед вырезанием символов.

border_width = 1
new_pix = pix[border_width:-border_width,border_width:-border_width]

вырезание персонажей

Поскольку между персонажами нет связи, для вырезания символов можно использовать относительно простой «метод проецирования». Принцип заключается в том, чтобы сначала спроецировать бинаризованное изображение в вертикальном направлении и оценить границу сегментации в соответствии с экстремальным значением после проецирования. Затем сегментированное маленькое изображение проецируется в горизонтальном направлении.

Код:

def vertical_image(image):
    height, width = image.shape
    h = [0] * width
    for x in range(width):
        for y in range(height):
            s = image[y, x]
            if s == 255:
                h[x] += 1
    new_image = np.zeros(image.shape, np.uint8)
    for x in range(width):
        cv2.line(new_image, (x, 0), (x, h[x]), 255, 1)
    cv2.imshow('vert_image', new_image)
    cv2.waitKey()
cv2.destroyAllWindows()

общий код

from PIL import Image
import cv2
import numpy as np
import os
import uuid
 
 
def clean_bg(filename):
    im = Image.open(filename)
    im_gray = im.convert('L')
    image = np.array(im_gray)
    threshold = 100  # 阈值
    pix = (image > threshold) * 255
    border_width = 1
    new_image = pix[border_width:-border_width, border_width:-border_width]
    return new_image
 
 
def get_col_rect(image):
    height, width = image.shape
    h = [0] * width
    for x in range(width):
        for y in range(height):
            s = image[y, x]
            if s == 0:
                h[x] += 1
    col_rect = []
    in_line = False
    start_line = 0
    blank_distance = 1
    for i in range(len(h)):
        if not in_line and h[i] >= blank_distance:
            in_line = True
            start_line = i
        elif in_line and h[i] < blank_distance:
            rect = (start_line, i)
            col_rect.append(rect)
            in_line = False
            start_line = 0
    return col_rect
 
 
def get_row_rect(image):
    height, width = image.shape
    h = [0] * height
    for y in range(height):
        for x in range(width):
            s = image[y, x]
            if s == 0:
                h[y] += 1
    in_line = False
    start_line = 0
    blank_distance = 1
    row_rect = (0, 0)
    for i in range(len(h)):
        if not in_line and h[i] >= blank_distance:
            in_line = True
            start_line = i
        elif in_line and i == len(h)-1:
            row_rect = (start_line, i)
        elif in_line and h[i] < blank_distance:
            row_rect = (start_line, i)
            break
    return row_rect
 
 
def get_block_image(image, col_rect):
    col_image = image[0:image.shape[0], col_rect[0]:col_rect[1]]
    row_rect = get_row_rect(col_image)
    if row_rect[1] != 0:
        block_image = image[row_rect[0]:row_rect[1], col_rect[0]:col_rect[1]]
    else:
        block_image = None
    return block_image
 
 
def clean_bg(filename):
    im = Image.open(filename)
    im_gray = im.convert('L')
    image = np.array(im_gray)
    threshold = 100  # 阈值
    pix = (image > threshold) * 255
    border_width = 2
    new_image = pix[border_width:-border_width, border_width:-border_width]
    return new_image
 
def split(filename):
    image = clean_bg(filename)
    col_rect = get_col_rect(image)
    for cols in col_rect:
        block_image = get_block_image(image, cols)
        if block_image is not None:
            new_image_filename = 'letters/' + str(uuid.uuid4()) + '.png'
            cv2.imwrite(new_image_filename, block_image)
 
 
if __name__ == '__main__':
    for filename in os.listdir('captchas'):
        current_file = 'captchas/' + filename
        split(current_file)
        print('split file:%s' % current_file)

Подготовка набора данных

После завершения вырезания изображения необходимо создать образец вырезанных букв из этикеток. То есть отсортировать сегментированные символы в правильную классификацию. Самый распространенный способ – ручной уход.

Из-за большого количества изображений здесь для идентификации используется Tesseract-OCR.

Официальный адрес проекта:GitHub.com/t-gentle-act-oh…

Адрес установочного пакета Windows:GitHub.com/UB-Мангейм…

Установка Tesseract-OCR

Скачав установочный пакет, вы можете запустить установку напрямую, главное — установить переменные окружения.

  • Добавьте каталог установки (D:\Program Files (x86)\Tesseract-OCR) в PATH.
  • Создайте новую системную переменную TESSDATA_PREFIX с путем к папке tessdata (D:\Program Files (x86)\Tesseract-OCR\tessdata)
  • Установите пакет Python pytesseract (pip install pytesseract)

Использование Tesseract-OCR

Он очень прост в использовании, код выглядит следующим образом:

from PIL import Image
import pytesseract
import os
 
 
def copy_to_dir(filename):
    image = Image.open(filename)
    code = pytesseract.image_to_string(image, config="-c tessedit"
                                                     "_char_whitelist=ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
                                                     " --psm 10"
                                                     " -l osd"
                                                     " ")
    if not os.path.exists("dataset/" + code):
        os.mkdir("dataset/" + code)
    image.save("dataset/" + code + filename.replace("letters", ""))
    image.close()
 
 
if __name__ == "__main__":
    for filename in os.listdir('letters'):
        current_file = 'letters/' + filename
        copy_to_dir(current_file)
        print(current_file)

Так как точность распознавания Tesseract-OCR очень низкая, его вообще нельзя использовать, отказаться~ или нужно сортировать вручную.

Единый размер изображения

После завершения ручной обработки обнаружилось, что размер вырезанных картинок разный. Размер изображения должен быть унифицирован перед распознаванием символов.

Конкретный метод реализации:

import cv2
 
def image_resize(filename):
    img = cv2.imread(filename, cv2.IMREAD_GRAYSCALE) #读取图片时采用单通道
    print(img)
    if img.shape[0] != 10 or img.shape[1] != 6:
        img = cv2.resize(img, (6, 10), interpolation=cv2.INTER_CUBIC)
        print(img)
        cv2.imwrite(filename, img)

При использовании cv2.resize ввод параметра ширина × высота × канал, здесь один канал, варианты интерполяции:

  • Inter_nearest Ближайший соседний интерполяция
  • Билинейная интерполяция INTER_LINEAR (настройка по умолчанию)
  • INTER_AREA использует отношение площади пикселя для передискретизации. Вероятно, это предпочтительный метод прореживания изображения, поскольку он дает текстурированные результаты без облаков. Но когда изображение масштабируется, это похоже на метод INTER_NEAREST.
  • INTER_CUBIC Бикубическая интерполяция окрестности 4x4 пикселя
  • INTER_LANCZOS4 Интерполяция Lanczos окрестности 8x8 пикселей

Кроме того, чтобы сделать данные более удобными в использовании, изображение можно бинаризировать и нормализовать. Конкретный код выглядит следующим образом:

import cv2
import numpy as np
 
def image_normalize(filename):
    img = cv2.imread(filename, cv2.IMREAD_GRAYSCALE) #读取图片时采用单通道
    if img.shape[0] != 10 or img.shape[1] != 6:
        img = cv2.resize(img, (6, 10), interpolation=cv2.INTER_CUBIC)
    normalized_img = np.zeros((6, 10))  # 归一化
    normalized_img = cv2.normalize(img, normalized_img, 0, 1, cv2.NORM_MINMAX)
    cv2.imwrite(filename, normalized_img)

Нормализованный тип может иметь следующие значения:

  • NORM_MINMAX: значение массива преобразуется или масштабируется в указанный диапазон, чаще используется линейная нормализация.
  • NORM_INF: Определение этого типа не найдено Согласно соответствующему пункту OpenCV 1, это может быть C-норма нормализованного массива (максимальное значение абсолютного значения)
  • NORM_L1 : L1-норма нормализованного массива (сумма абсолютных значений)
  • NORM_L2: (евклидова) L2-норма нормализованного массива

Распознавание символов

Изображение символа имеет ширину 6 пикселей и высоту 10. Теоретически 60 признаков можно определить самым простым и грубым способом: значением пикселя выше 60 точек пикселя. Однако очевидно, что столь высокая размерность неизбежно приведет к избыточному объему вычислений, который можно соответствующим образом сократить. Например:

  • Количество черных пикселей на каждой строке, можно получить 10 признаков
  • Количество черных пикселей в каждом столбце, можно получить 6 признаков

from sklearn.neighbors import KNeighborsClassifier
import os
from sklearn import preprocessing
import cv2
import numpy as np
import warnings
warnings.filterwarnings(module='sklearn*', action='ignore', category=DeprecationWarning)
 
 
def get_feature(file_name):
    img = cv2.imread(file_name, cv2.IMREAD_GRAYSCALE)  # 读取图片时采用单通道
    height, width = img.shape
 
    pixel_cnt_list = []
    for y in range(height):
        pix_cnt_x = 0
        for x in range(width):
            if img[y, x] == 0:  # 黑色点
                pix_cnt_x += 1
 
        pixel_cnt_list.append(pix_cnt_x)
 
    for x in range(width):
        pix_cnt_y = 0
        for y in range(height):
            if img[y, x] == 0:  # 黑色点
                pix_cnt_y += 1
 
        pixel_cnt_list.append(pix_cnt_y)
 
    return pixel_cnt_list
 
 
if __name__ == "__main__":
    test = get_feature("dataset/K/04a0844c-12f2-4344-9b78-ac1d28d746c0.png")
    category = []
    features = []
    for dir_name in os.listdir('dataset'):
        for filename in os.listdir('dataset/' + dir_name):
            category.append(dir_name)
            current_file = 'dataset/' + dir_name + '/' + filename
            feature = get_feature(current_file)
            features.append(feature)
            # print(current_file)
    le = preprocessing.LabelEncoder()
    label = le.fit_transform(category)
 
    model = KNeighborsClassifier(n_neighbors=1)
    model.fit(features, label)
    predicted= model.predict(np.array(test).reshape(1, -1))
    print(predicted)
    print(le.inverse_transform(predicted))

Здесь непосредственно используется метод KNN в sklearn, для получения дополнительной информации см.:Классификация KNN с помощью Scikit-learn

Вознаграждение автора WeChat Pay标点符 wechat qrcodeAlipay标点符 alipay qrcode