Получить местоположение всего текста, присутствующего в изображении, используя opencv - PullRequest
11 голосов
/ 17 января 2020

У меня есть это изображение, которое содержит текст (цифры и алфавиты). Я хочу получить местоположение всего текста и чисел, присутствующих на этом изображении. Также я хочу извлечь весь текст, а также.

enter image description here

Как мне получить кординаты, а также весь текст (цифры и алфавиты) по моему образу. Например, 10B, 44, 16, 38, 22B et c

Ответы [ 3 ]

13 голосов
/ 15 февраля 2020

Вот потенциальный подход с использованием морфологических операций для фильтрации нетекстовых контуров. Идея такова:

  1. Получить двоичное изображение. Загрузить изображение, оттенки серого, затем Порог Оцу

  2. Удаление горизонтальных и вертикальных линий. Создание горизонтальных и вертикальных ядер с использованием cv2.getStructuringElement, затем удаление линий с помощью cv2.drawContours

  3. Удаление диагональных линий, окружностей и изогнутых контуров. Фильтр с использованием области контура cv2.contourArea и аппроксимации контура cv2.approxPolyDP, чтобы изолировать текстовые контуры

  4. Извлечение текстовых областей интереса и распознавания текста. Найдите контуры и фильтр для областей интереса, а затем распознавания текста с помощью Pytesseract .


Удалены горизонтальные линии, выделенные зеленым цветом

enter image description here

Удалены вертикальные линии

enter image description here

Удалены различные нетекстовые контуры (диагональные линии, круглые объекты и кривые)

enter image description here

Обнаруженные области текста

enter image description here

import cv2
import numpy as np
import pytesseract

pytesseract.pytesseract.tesseract_cmd = r"C:\Program Files\Tesseract-OCR\tesseract.exe"

# Load image, grayscale, Otsu's threshold
image = cv2.imread('1.jpg')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]
clean = thresh.copy()

# Remove horizontal lines
horizontal_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (15,1))
detect_horizontal = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, horizontal_kernel, iterations=2)
cnts = cv2.findContours(detect_horizontal, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
    cv2.drawContours(clean, [c], -1, 0, 3)

# Remove vertical lines
vertical_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1,30))
detect_vertical = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, vertical_kernel, iterations=2)
cnts = cv2.findContours(detect_vertical, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
    cv2.drawContours(clean, [c], -1, 0, 3)

cnts = cv2.findContours(clean, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
    # Remove diagonal lines
    area = cv2.contourArea(c)
    if area < 100:
        cv2.drawContours(clean, [c], -1, 0, 3)
    # Remove circle objects
    elif area > 1000:
        cv2.drawContours(clean, [c], -1, 0, -1)
    # Remove curve stuff
    peri = cv2.arcLength(c, True)
    approx = cv2.approxPolyDP(c, 0.02 * peri, True)
    x,y,w,h = cv2.boundingRect(c)
    if len(approx) == 4:
        cv2.rectangle(clean, (x, y), (x + w, y + h), 0, -1)

open_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (2,2))
opening = cv2.morphologyEx(clean, cv2.MORPH_OPEN, open_kernel, iterations=2)
close_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3,2))
close = cv2.morphologyEx(opening, cv2.MORPH_CLOSE, close_kernel, iterations=4)
cnts = cv2.findContours(close, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
    x,y,w,h = cv2.boundingRect(c)
    area = cv2.contourArea(c)
    if area > 500:
        ROI = image[y:y+h, x:x+w]
        ROI = cv2.GaussianBlur(ROI, (3,3), 0)
        data = pytesseract.image_to_string(ROI, lang='eng',config='--psm 6')
        if data.isalnum():
            cv2.rectangle(image, (x, y), (x + w, y + h), (36,255,12), 2)
            print(data)

cv2.imwrite('image.png', image)
cv2.imwrite('clean.png', clean)
cv2.imwrite('close.png', close)
cv2.imwrite('opening.png', opening)
cv2.waitKey()
8 голосов
/ 15 февраля 2020

Хорошо, вот еще одно возможное решение. Я знаю, что вы работаете с Python - я работаю с C ++. Я дам вам несколько идей и, надеюсь, если вы захотите, вы сможете реализовать этот ответ.

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

Я пытаюсь не использовать предварительную обработку, потому что: 1) фильтры и морфологические стадии могут ухудшить качество сгустков и 2) ваши целевые сгустки обладают некоторыми характеристиками, которые мы могли бы использовать, в основном: форматное соотношение и площадь .

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

Идея состоит в том, что я буду фильтровать все, что не попадает в предварительно рассчитанные значения. Я проверил символы (цифры и буквы) и пришел с минимальными, максимальными значениями площади и минимальным соотношением сторон (здесь, соотношение между высотой и шириной).

Давайте работать над алгоритмом. Начните с чтения изображения и изменения его размера до половины размеров. Ваше изображение слишком велико. Преобразовать в оттенки серого и получить двоичное изображение через отсу, вот в псевдокоде:

//Read input:
inputImage = imread( "diagram.png" );

//Resize Image;
resizeScale = 0.5;

inputResized = imresize( inputImage, resizeScale );

//Convert to grayscale;
inputGray = rgb2gray( inputResized );

//Get binary image via otsu:
binaryImage = imbinarize( inputGray, "Otsu" );

Круто. Мы будем работать с этим изображением. Вам нужно изучить каждый белый шарик и применить «фильтр свойств» . Я использую подключенных компонентов со статистикой к l oop через каждый шарик и получаю его площадь и соотношение сторон, в C ++ это делается следующим образом:

//Prepare the output matrices:
cv::Mat outputLabels, stats, centroids;
int connectivity = 8;

//Run the binary image through connected components:
int numberofComponents = cv::connectedComponentsWithStats( binaryImage, outputLabels, stats, centroids, connectivity );

//Prepare a vector of colors – color the filtered blobs in black
std::vector<cv::Vec3b> colors(numberofComponents+1);
colors[0] = cv::Vec3b( 0, 0, 0 ); // Element 0 is the background, which remains black.

//loop through the detected blobs:
for( int i = 1; i <= numberofComponents; i++ ) {

    //get area:
    auto blobArea = stats.at<int>(i, cv::CC_STAT_AREA);

    //get height, width and compute aspect ratio:
    auto blobWidth = stats.at<int>(i, cv::CC_STAT_WIDTH);
    auto blobHeight = stats.at<int>(i, cv::CC_STAT_HEIGHT);
    float blobAspectRatio = (float)blobHeight/(float)blobWidth;

    //Filter your blobs…

};

Теперь мы будет применять фильтр свойств. Это всего лишь сравнение с заранее рассчитанными порогами. Я использовал следующие значения:

Minimum Area: 40  Maximum Area:400
MinimumAspectRatio:  1

Внутри вашего for l oop сравните текущие свойства BLOB-объектов с этими значениями. Если тесты положительные, вы «рисуете» черную каплю. Продолжая в for l oop:

    //Filter your blobs…

    //Test the current properties against the thresholds:
    bool areaTest =  (blobArea > maxArea)||(blobArea < minArea);
    bool aspectRatioTest = !(blobAspectRatio > minAspectRatio); //notice we are looking for TALL elements!

    //Paint the blob black:
    if( areaTest || aspectRatioTest ){
        //filtered blobs are colored in black:
        colors[i] = cv::Vec3b( 0, 0, 0 );
    }else{
        //unfiltered blobs are colored in white:
        colors[i] = cv::Vec3b( 255, 255, 255 );
    }

После l oop, создайте отфильтрованное изображение:

cv::Mat filteredMat = cv::Mat::zeros( binaryImage.size(), CV_8UC3 );
for( int y = 0; y < filteredMat.rows; y++ ){
    for( int x = 0; x < filteredMat.cols; x++ )
    {
        int label = outputLabels.at<int>(y, x);
        filteredMat.at<cv::Vec3b>(y, x) = colors[label];
    }
}

И… это в значительной степени все. Вы отфильтровали все элементы, которые не похожи на то, что вы ищете. Запустив алгоритм, вы получите следующий результат:

enter image description here

Я также нашел Bounding Boxs для BLOB-объектов, чтобы лучше визуализировать результаты:

enter image description here

Как видите, некоторые элементы обнаружены с ошибками. Вы можете уточнить «фильтр свойств», чтобы лучше идентифицировать символы, которые вы ищете. Более глубокое решение, включающее немного машинного обучения, требует построения «идеального вектора признаков», извлечения признаков из BLOB-объектов и сравнения обоих векторов с помощью меры сходства. Вы также можете применить некоторую post -обработку для улучшения результатов ...

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

4 голосов
/ 13 февраля 2020

Одним из способов является использование раздвижного окна (это дорого).

Определите размер символов на изображении (все символы имеют тот же размер, что и на изображении) и установите размер окна. Попробуйте тессеракт для обнаружения (входное изображение требует предварительной обработки). Если окно обнаруживает символы последовательно, сохраните координаты окна. Объедините координаты и получите регион по персонажам.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...