Uzyskaj lokalizację całego tekstu obecnego na obrazie za pomocą opencv

11

Mam ten obraz, który zawiera tekst (liczby i alfabety). Chcę uzyskać lokalizację całego tekstu i liczb obecnych na tym obrazie. Chcę też wyodrębnić cały tekst.

wprowadź opis zdjęcia tutaj

Jak uzyskać cordinates, a także cały tekst (cyfry i alfabety) na moim obrazie. Na przykład 10B, 44, 16, 38, 22B itp

Pulkit Bhatnagar
źródło
jaka jest twoja wersja tensorflow? Jeśli masz wersję 2.1, spróbuj zainstalować 2.0
gellezzz
1
Rzucanie nagród za złe pytania nie jest dobrą praktyką. Nie pokazałeś żadnej wiedzy, jak to zrobić, więc wygląda na to, że po prostu próbujesz zachęcić programistów do kodowania kompletnego rozwiązania w zamian za kilka punktów rep. Nie oczekuję, że z tego powodu zobaczę idealne odpowiedzi, ale wierzę, że możesz uzyskać lepsze rozwiązania na zewnętrznych stronach internetowych, jeśli płacisz ludziom za ich czas.
karlphillip
@ karlphillip bardzo przepraszam, ale jestem początkujący Potrzebuję czegoś, aby zacząć, prawda? Czy możesz mi pomóc z tym
Pulkit Bhatnagar

Odpowiedzi:

13

Oto potencjalne podejście wykorzystujące operacje morfologiczne do filtrowania konturów nietekstowych. Chodzi o to:

  1. Uzyskaj obraz binarny. Załaduj obraz, skalę szarości, a następnie próg Otsu

  2. Usuń poziome i pionowe linie. Twórz jądra poziome i pionowe za pomocą, cv2.getStructuringElementa następnie usuwaj linie za pomocącv2.drawContours

  3. Usuń ukośne linie, obiekty kołowe i zakrzywione kontury. Filtruj według obszaru konturu cv2.contourArea i aproksymacji konturu, cv2.approxPolyDP aby wyizolować kontury nietekstowe

  4. Wyodrębnij ROI i OCR tekstu. Znajdź kontury i filtruj dla ROI, a następnie OCR za pomocą Pytesseract .


Usunięto poziome linie podświetlone na zielono

wprowadź opis zdjęcia tutaj

Usunięto pionowe linie

wprowadź opis zdjęcia tutaj

Usunięto różne kontury nietekstowe (linie ukośne, obiekty kołowe i krzywe)

wprowadź opis zdjęcia tutaj

Wykryte regiony tekstowe

wprowadź opis zdjęcia tutaj

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()
nathancy
źródło
Dobry pomysł, aby najpierw usunąć te linie.
karlphillip
zły pomysł, aby usunąć również części liter ...
Walter Tross
8

Dobra, oto inne możliwe rozwiązanie. Wiem, że pracujesz z Pythonem - pracuję z C ++. Dam ci kilka pomysłów i mam nadzieję, że jeśli zechcesz, będziesz w stanie zastosować tę odpowiedź.

Główną ideą jest całkowite niestosowanie przetwarzania wstępnego (przynajmniej na początkowym etapie) i skupienie się na każdym znaku docelowym, uzyskanie niektórych właściwości i filtrowanie każdego obiektu blob zgodnie z tymi właściwościami.

Staram się nie używać wstępnego przetwarzania, ponieważ: 1) Filtry i etapy morfologiczne mogą obniżyć jakość obiektów blob oraz 2) Twoje docelowe obiekty blob wydają się wykazywać pewne cechy, które moglibyśmy wykorzystać, głównie: współczynnik kształtu i obszar .

Sprawdź to, wszystkie cyfry i litery wydają się być wyższe niż szersze… ponadto wydają się różnić w obrębie pewnej wartości powierzchni. Na przykład chcesz odrzucić obiekty „za szerokie” lub „za duże” .

Chodzi o to, że będę filtrować wszystko, co nie mieści się w wstępnie obliczonych wartościach. Sprawdziłem znaki (cyfry i litery) i uzyskałem minimalne, maksymalne wartości powierzchni i minimalny współczynnik kształtu (tutaj stosunek wysokości do szerokości).

Pracujmy nad algorytmem. Zacznij od przeczytania obrazu i zmiany jego rozmiaru do połowy wymiarów. Twój obraz jest zdecydowanie za duży. Konwertuj na skalę szarości i uzyskaj obraz binarny przez otsu, oto pseudo-kod:

//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" );

Fajne. Będziemy pracować z tym obrazem. Musisz zbadać każdą białą kroplę i zastosować „filtr właściwości” . Korzystam z połączonych komponentów ze statystykami, aby przeglądać każdą kroplę i uzyskiwać jej powierzchnię i proporcje, w C ++ odbywa się to w następujący sposób:

//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

};

Teraz zastosujemy filtr właściwości. Jest to tylko porównanie z wstępnie obliczonymi progami. Użyłem następujących wartości:

Minimum Area: 40  Maximum Area:400
MinimumAspectRatio:  1

Wewnątrz forpętli porównaj bieżące właściwości obiektu blob z tymi wartościami. Jeśli testy są pozytywne, „malujesz” kroplę na czarno. Kontynuując wewnątrz forpętli:

    //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 );
    }

Po pętli zbuduj filtrowany obraz:

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];
    }
}

I… to wszystko. Przefiltrowano wszystkie elementy, które nie są podobne do tego, czego szukasz. Po uruchomieniu algorytmu otrzymujesz ten wynik:

wprowadź opis zdjęcia tutaj

Dodatkowo znalazłem ramki ograniczające obiektów blob, aby lepiej wizualizować wyniki:

wprowadź opis zdjęcia tutaj

Jak widać, niektóre elementy zostały wykryte. Możesz zawęzić „filtr właściwości”, aby lepiej identyfikować poszukiwane znaki. Głębsze rozwiązanie, wymagające odrobiny uczenia maszynowego, wymaga zbudowania „idealnego wektora cech”, wydobycia cech z obiektów blob i porównania obu wektorów za pomocą miary podobieństwa. Możesz również zastosować postprocessing, aby poprawić wyniki ...

Cokolwiek, człowieku, twój problem nie jest trywialny ani łatwy do skalowania, a ja tylko daję ci pomysły. Mamy nadzieję, że będziesz w stanie wdrożyć swoje rozwiązanie.

eldesgraciado
źródło
Każda szansa na konwersję tego samego programu do python
Pulkit Bhatnagar
@PulkitBhatnagar Tak, oczywiście. Trzymaj się mocno, za kilka minut przygotuję idealny port.
eldesgraciado
?? zrobiłeś to, abym mógł ci przyznać nagrodę
Pulkit Bhatnagar
O tak. Jest mi strasznie przykro, proszę pana, wpadłem w kłopoty, ale konwersja przychodzi miło. Tylko poczekaj. Dzięki.
eldesgraciado
nigdy się nie zastanawiałem, że to może być sarkazm.
Pulkit Bhatnagar
4

Jedną z metod jest użycie przesuwanego okna (jest to kosztowne).

Określ rozmiar znaków na obrazie (wszystkie znaki mają taki sam rozmiar jak na obrazie) i ustaw rozmiar okna. Wypróbuj tesseract w celu wykrycia (obraz wejściowy wymaga wstępnego przetwarzania). Jeśli okno wykrywa znaki kolejno, zapisz współrzędne okna. Scal współrzędne i uzyskaj region na postacie.

ratować Ziemię
źródło
Myślę, że 100bounty jest za odpowiedź
Himanshu Poddar