Глобально моя задача — определить сходство/непохожесть двух файлов .jpg. Ниже я опишу процесс более подробно. У меня есть пять (на самом деле их больше) файлов шаблонов .jpg. И у меня есть новый файл .jpg, который я должен сопоставить с каждым файлом .jpg шаблона, чтобы принять решение: похож ли новый файл .jpg на какой-либо из файлов .jpg шаблона или нет.
Коррелировать целые файлы в моем случае — плохая идея, так как ошибка большая. Поэтому я придумал способ «разрезать» новый файл на 12 равных частей (фрагментов) (то есть на 12 файлов .jpg) и искать каждый отдельный фрагмент в шаблоне.
Для этого я использовал учебник https://docs.opencv.org/4.x/dc/dc3/tutorial_py_matcher.html
Но проблема в том, что фрагменты из нового .jpg файла крайне некорректно сопоставляются с шаблоном.
Ниже я покажу пример: Давайте возьмем документ ниже в качестве шаблона.
А документ ниже как новый документ (схематически я разрезаю его на 12 частей, то есть получаю на вход один документ, но этот один документ я разрезаю на 12 частей (фрагментов) (то есть 12 новых файлов))
Далее взгляните на мой код. Суть в том, что я беру каждый из 12 фрагментов и ищу этот фрагмент в шаблоне.
def match_slices_in_template(path_template):
directory_in_str = 'slices'
directory = os.fsencode(directory_in_str)
img1 = cv.imread(path_template, cv.IMREAD_GRAYSCALE) # queryImage
good = []
for slice_image in os.listdir(directory):
print(slice_image)
filename = os.fsdecode(slice_image)
img2 = cv.imread(f'slices/{filename}', cv.IMREAD_GRAYSCALE) # trainImage
# Initiate SIFT detector
sift = cv.SIFT_create()
# find the keypoints and descriptors with SIFT
kp1, des1 = sift.detectAndCompute(img1, None)
kp2, des2 = sift.detectAndCompute(img2, None)
# BFMatcher with default params
bf = cv.BFMatcher()
matches = bf.knnMatch(des1, des2, k=2)
# Apply ratio test
for m, n in matches:
if m.distance < 0.3 * n.distance:
good.append([m])
# cv.drawMatchesKnn expects list of lists as matches.
img3 = cv.drawMatchesKnn(img1, kp1, img2, kp2, good, None, flags=cv.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)
plt.imshow(img3), plt.show()
print(match_slices_in_template('Bill_1.jpg'))
Но результат поиска совершенно неверен, взгляните на примеры графиков, построенных matplotlib.
В моем примере на самом деле два файла разные (хотя у них много общего). Но программа определяет их как очень похожие.
Итак, вы, наверное, поняли суть моего вопроса: как улучшить производительность алгоритма, чтобы он определял сходство/различие точнее, чем сейчас.
@Спасибо за совет. А что вы потом думаете, если я заранее удалю весь текст из .jpg файла, т.е. оставлю в документе только таблицу. И сравните ее с таблицей из другого файла .jpg. оставив концепцию этого вопроса прежней (разрезание нового файла на фрагменты). Будет ли это эффективнее или нет?
Удаление переменного текста (заполненного содержимого), но сохранение фиксированного текста (меток форм и описаний) имело бы смысл. --разрезать на фрагменты нет смысла. вы сказали "ошибка большая", что не показано и не аргументировано. -- вы еще не обсудили обязательный шаг после сопоставления. Вы следили за каким-либо материалом, который останавливается после сопоставления и вытягивания спичек? тогда вы смотрите не на тот материал.
@ChristophRackwitz А какой шаг должен быть после сравнения, скажите пожалуйста?
подгонка модели к подмножеству совпадений. один подход использует RANSAC. модель может быть гомографией или меньшим преобразованием. findHomography
— используемая функция. это тоже не конец. поскольку RANSAC является вероятностным, вам необходимо оценить модель на предмет ее правдоподобия. если у вас есть сканы, гомография не должна иметь перспективы, не должно быть растяжения (т. е. масштабирование одинаковое), а если скан был помещен правой стороной вверх, вращения быть не должно. альтернативно, углы шаблона, нанесенные на скан, должны примерно совпадать с углами скана, и наоборот.
вам следовало перейти от урока, на который вы ссылались, к следующему: i.sstatic.net/jtd4eStF.png (найдите этот список на один уровень выше ссылки в вашем вопросе). да, документация могла бы быть лучше, поскольку в ней должна быть ссылка на следующий урок прямо внизу предыдущего.
Глобально моя задача — определить сходство/непохожесть двух файлов .jpg, думаю, что учитывая поставленную цель, ваш подход не совсем сработает для всех случаев. Например, посмотрите на нижнюю центральную часть, структурно они почти не имеют ничего общего с основным изображением. Я бы предпочел использовать OCR для всего изображения, а затем использовать слова для классификации документов, я мог бы написать пример...
Я написал пример, который поможет вам классифицировать документы, опираясь на текст, а не на структурное сходство между документами.
Моя папка организована следующим образом:
У меня есть два обучающих изображения и три тестовых изображения. Обучающие изображения находятся в папке шаблонов, а изображения, которые я хочу классифицировать, — в папке toBeClassified. Вот мои входные изображения:
Теперь, когда мы разобрались с этим, вот функция, которая может получать текст из изображений:
def text_from_image(filename):
'''
Load image as grayscale by using cv2.irmead(_, 0)
extract text using pytesseract, we use the text for classification
'''
imGray = cv2.imread(filename, 0)
text = pytesseract.image_to_string(imGray)
return text
Теперь, когда у нас есть текст, мы можем использовать его вместе с некоторыми метками, которые мы помещаем в качестве обучающих ответов для обучения модели:
texts = [text_from_image("Templates/"+filename) for filename in os.listdir("Templates/")] # get the texts
labels = ["Shipping", "Invoice"] # label the texts
Эти строки используются для получения модели и ее обучения на шаблонах:
model = make_pipeline(TfidfVectorizer(), MultinomialNB()) # init the model
model.fit(texts, labels) # fit the model
Теперь мы можем использовать два подхода: либо мы просто удовлетворяемся классом с наибольшей вероятностью, либо мы используем прогнозирование вероятностей.
Первый подход:
def classify_with_model(filename, model):
'''
We pass the filename of the images to be classified and the model to get the
class with the highest probability, not very good but good for now
'''
text = text_from_image(filename)
category = model.predict([text])[0]
return category
Если мы пройдемся по тестовым изображениям, мы получим:
for filename in os.listdir("toBeClassified/"): # loop through the test images
category = classify_with_model("toBeClassified/"+filename, model) # get the class with the highest prob
print(f'Document {filename} is classified as {category}') # print out results
# Document Bill1.png is classified as Invoice
# Document Shipping1.jpg is classified as Shipping
# Document ShippingInvoice1.png is classified as Invoice
Обратите внимание на последнее изображение — это своего рода гибрид, который я нашел в Интернете. Поэтому в некоторых случаях рассмотрение вероятностей весьма важно. Как шутят, когда речь идет об учениках СНМ: Здоровый человек идет к врачу на обследование на рак. Доктор использует современную модель машинного обучения SVM со 100% точностью выявления различных типов рака. После теста врач возвращается и говорит: «Хорошие новости и плохие новости. Хорошие новости: наша модель идеально подходит для диагностики всех типов рака. Плохие новости: она не знает, как сказать, что вы здоровы. "
В любом случае, нет времени на юмор или мои попытки его реализовать. Мы идем с вероятностями:
def classify_with_probability(filename, model):
'''
This is to classify with probability, a bit better to see the confidences
'''
text = text_from_image(filename)
probabilities = model.predict_proba([text])[0] # get probabilities for each class
categories = model.classes_ # get the class labels
bestMatchIdx = probabilities.argmax() # index of the highest probability
bestMatch = categories[bestMatchIdx] # class with the highest probability
confidence = probabilities[bestMatchIdx] # probability of the best match
return bestMatch, dict(zip(categories, probabilities)) # return everythin
И результаты:
for filename in os.listdir("toBeClassified/"):
category = classify_with_probability("toBeClassified/"+filename, model)
print(f'Document {filename} is classified as {category[0]} with the following confidence:')
print(category[1])
print("_______________________")
#Document Bill1.png is classified as Invoice with the following confidence:
#{'Invoice': 0.5693790615221315, 'Shipping': 0.4306209384778673}
#_______________________
#Document Shipping1.jpg is classified as Shipping with the following confidence:
#{'Invoice': 0.38458825025403914, 'Shipping': 0.6154117497459587}
#_______________________
#Document ShippingInvoice1.png is classified as Invoice with the following confidence:
#{'Invoice': 0.5519774748181495, 'Shipping': 0.4480225251818504}
#_______________________
Здесь мы также видим вероятности, это может быть полезно, если вы хотите одновременно классифицировать изображения по нескольким классам, например, счет-фактура и форма доставки.
Я надеюсь, что это вам каким-то образом поможет, как я уже сказал, и, как упомянул Кристоф, использование функций в этих документах будет дикой попыткой.
Импорт:
import cv2
import pytesseract
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.naive_bayes import MultinomialNB
from sklearn.pipeline import make_pipeline
import os
pytesseract.pytesseract.tesseract_cmd = "C:/Program Files/Tesseract-OCR/tesseract.exe"
Спасибо большое, что обратили внимание на мой вопрос, а также спасибо за такой подробный ответ. Конечно, мне нужно время, чтобы прочитать и понять, как здесь все устроено. Я вернусь к тебе!
Конечно, @Paul, дайте мне знать, если у вас возникнут вопросы!
Во-первых, я оценил вашу шутку)) Вы довольно забавный человек. Во-вторых, я начал изучать ваш код и у меня есть несколько вопросов по поводу второго подхода (прогнозирования вероятностей):
1) в функции classify_with_probability последняя переменная доверительность нигде не используется. Это необходимо? 2) в следующей части кода есть строка f'Документ {имя файла} классифицируется как {категория[0]} со следующей достоверностью:' и сразу под строкой, где отображаются вероятности относительно каждого шаблона. Я немного запутался: либо я не правильно понял ход Ваших мыслей, либо Вы что-то не так написали
1) вы можете удалить эту строку, эта информация все равно возвращается в zip(categoties, probabilities)
@Paul
2) не уверен, что вы имеете в виду? Идея здесь в том, что вы выбираете класс с наибольшей вероятностью, но также важно видеть вероятности всех классов. Вот почему вы видите категории с соответствующими пробами справа. @Paul Спрашивайте больше, если вы все еще в чем-то не уверены.
Да, я уже понял этот момент. Извините, я не совсем понял, прежде чем спросить.
Интересно, а не подскажете, почему два совершенно одинаковых документа классифицируются с вероятностью 0,5-0,6, а не с вероятностью 1?
Надо будет посмотреть изображения и проверить самому. Хотя я думаю, что вы не совсем поняли методологию этого подхода. Если некоторые слова встречаются в обоих шаблонных документах, вероятность может быть высокой для обоих обученных классов. Эти слова также можно удалить (дата, адрес, имя). Если вы хотите обсудить это дальше, предлагаю пойти в чат.
Давайте продолжим обсуждение в чате.
соответствия недостаточно. ты забыл то, что будет после. -- эти фрагменты очень голые, почти безликие. это плохая ситуация. не делай этого. совпадение на всей странице.