Найдите фрагмент на всем изображении

Глобально моя задача — определить сходство/непохожесть двух файлов .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.

В моем примере на самом деле два файла разные (хотя у них много общего). Но программа определяет их как очень похожие.

Итак, вы, наверное, поняли суть моего вопроса: как улучшить производительность алгоритма, чтобы он определял сходство/различие точнее, чем сейчас.

соответствия недостаточно. ты забыл то, что будет после. -- эти фрагменты очень голые, почти безликие. это плохая ситуация. не делай этого. совпадение на всей странице.

Christoph Rackwitz 02.09.2024 00:40

@Спасибо за совет. А что вы потом думаете, если я заранее удалю весь текст из .jpg файла, т.е. оставлю в документе только таблицу. И сравните ее с таблицей из другого файла .jpg. оставив концепцию этого вопроса прежней (разрезание нового файла на фрагменты). Будет ли это эффективнее или нет?

Paul 02.09.2024 08:34

Удаление переменного текста (заполненного содержимого), но сохранение фиксированного текста (меток форм и описаний) имело бы смысл. --разрезать на фрагменты нет смысла. вы сказали "ошибка большая", что не показано и не аргументировано. -- вы еще не обсудили обязательный шаг после сопоставления. Вы следили за каким-либо материалом, который останавливается после сопоставления и вытягивания спичек? тогда вы смотрите не на тот материал.

Christoph Rackwitz 02.09.2024 09:47

@ChristophRackwitz А какой шаг должен быть после сравнения, скажите пожалуйста?

Paul 02.09.2024 09:49

подгонка модели к подмножеству совпадений. один подход использует RANSAC. модель может быть гомографией или меньшим преобразованием. findHomography — используемая функция. это тоже не конец. поскольку RANSAC является вероятностным, вам необходимо оценить модель на предмет ее правдоподобия. если у вас есть сканы, гомография не должна иметь перспективы, не должно быть растяжения (т. е. масштабирование одинаковое), а если скан был помещен правой стороной вверх, вращения быть не должно. альтернативно, углы шаблона, нанесенные на скан, должны примерно совпадать с углами скана, и наоборот.

Christoph Rackwitz 02.09.2024 09:59

вам следовало перейти от урока, на который вы ссылались, к следующему: i.sstatic.net/jtd4eStF.png (найдите этот список на один уровень выше ссылки в вашем вопросе). да, документация могла бы быть лучше, поскольку в ней должна быть ссылка на следующий урок прямо внизу предыдущего.

Christoph Rackwitz 02.09.2024 10:02

Глобально моя задача — определить сходство/непохожесть двух файлов .jpg, думаю, что учитывая поставленную цель, ваш подход не совсем сработает для всех случаев. Например, посмотрите на нижнюю центральную часть, структурно они почти не имеют ничего общего с основным изображением. Я бы предпочел использовать OCR для всего изображения, а затем использовать слова для классификации документов, я мог бы написать пример...

Tino D 02.09.2024 10:59
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
2
7
93
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я написал пример, который поможет вам классифицировать документы, опираясь на текст, а не на структурное сходство между документами.

Моя папка организована следующим образом:

У меня есть два обучающих изображения и три тестовых изображения. Обучающие изображения находятся в папке шаблонов, а изображения, которые я хочу классифицировать, — в папке 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 02.09.2024 12:36

Конечно, @Paul, дайте мне знать, если у вас возникнут вопросы!

Tino D 02.09.2024 12:46

Во-первых, я оценил вашу шутку)) Вы довольно забавный человек. Во-вторых, я начал изучать ваш код и у меня есть несколько вопросов по поводу второго подхода (прогнозирования вероятностей):

Paul 02.09.2024 23:06

1) в функции classify_with_probability последняя переменная доверительность нигде не используется. Это необходимо? 2) в следующей части кода есть строка f'Документ {имя файла} классифицируется как {категория[0]} со следующей достоверностью:' и сразу под строкой, где отображаются вероятности относительно каждого шаблона. Я немного запутался: либо я не правильно понял ход Ваших мыслей, либо Вы что-то не так написали

Paul 02.09.2024 23:07

1) вы можете удалить эту строку, эта информация все равно возвращается в zip(categoties, probabilities) @Paul

Tino D 03.09.2024 06:24

2) не уверен, что вы имеете в виду? Идея здесь в том, что вы выбираете класс с наибольшей вероятностью, но также важно видеть вероятности всех классов. Вот почему вы видите категории с соответствующими пробами справа. @Paul Спрашивайте больше, если вы все еще в чем-то не уверены.

Tino D 03.09.2024 06:26

Да, я уже понял этот момент. Извините, я не совсем понял, прежде чем спросить.

Paul 03.09.2024 08:38

Интересно, а не подскажете, почему два совершенно одинаковых документа классифицируются с вероятностью 0,5-0,6, а не с вероятностью 1?

Paul 03.09.2024 09:29

Надо будет посмотреть изображения и проверить самому. Хотя я думаю, что вы не совсем поняли методологию этого подхода. Если некоторые слова встречаются в обоих шаблонных документах, вероятность может быть высокой для обоих обученных классов. Эти слова также можно удалить (дата, адрес, имя). Если вы хотите обсудить это дальше, предлагаю пойти в чат.

Tino D 03.09.2024 09:37

Давайте продолжим обсуждение в чате.

Paul 03.09.2024 11:12

Другие вопросы по теме