Существует множество способов идентификации проверочного кода, например 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 PayAlipay