Dzielenie linii tekstu w zeskanowanym dokumencie

Próbuję znaleźć sposób na przełamanie podziału linii tekstu w zeskanowanym dokumencie, który został zaadaptowany. W tej chwili zapisuję wartości pikseli dokumentu jako niepodpisane inty od 0 do 255 i biorę średnią pikseli w każdej linii i dzielę linie na zakresy w zależności od tego, czy średnia wartości pikseli jest większa niż 250, a następnie biorę medianę każdego zakresu linii, dla których to dotyczy. Jednak metody te czasami zawodzą, ponieważ tam mogą być czarne plamy na obrazie.

Czy istnieje bardziej odporny na hałas sposób wykonania tego zadania?

EDIT: oto jakiś kod. "warped" to nazwa oryginalnego obrazu, "cuts" to miejsce, w którym chcę podzielić obraz.

warped = threshold_adaptive(warped, 250, offset = 10)
warped = warped.astype("uint8") * 255

# get areas where we can split image on whitespace to make OCR more accurate
color_level = np.array([np.sum(line) / len(line) for line in warped])
cuts = []
i = 0
while(i < len(color_level)):
    if color_level[i] > 250:
        begin = i
        while(color_level[i] > 250):
            i += 1
        cuts.append((i + begin)/2) # middle of the whitespace region
    else:
        i += 1

Edycja 2: Dodano przykładowy obraz Tutaj wpisz opis obrazka

Author: Alex, 2016-01-24

2 answers

Z obrazu wejściowego musisz zrobić tekst jako biały, a tło jako czarne

Tutaj wpisz opis obrazka

Następnie należy obliczyć kąt obrotu rachunku. Proste podejście polega na znalezieniu minAreaRect wszystkich białych punktów (findNonZero), a otrzymasz:

Tutaj wpisz opis obrazka

Następnie możesz obrócić rachunek, aby tekst był poziomy:

Tutaj wpisz opis obrazka

Teraz możesz obliczyć rzut poziomy (reduce). Możesz wziąć średnią wartość w każda linia. Zastosuj próg th na histogramie, aby uwzględnić jakiś szum na obrazie (tutaj użyłem 0, tzn. brak szumu). Linie z tylko tłem będą miały wartość >0, linie tekstowe będą miały wartość 0 w histogramie. Następnie weź średnią współrzędną kosza każdej ciągłej sekwencji białych koszy w histogramie. To będzie y współrzędna Twoich linii:

Tutaj wpisz opis obrazka

Tutaj Kod. Jest w C++, ale ponieważ większość pracy jest z OpenCV funkcje, powinno być łatwo konwertowane do Pythona. Przynajmniej możesz użyć tego jako odniesienia:

#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;

int main()
{
    // Read image
    Mat3b img = imread("path_to_image");

    // Binarize image. Text is white, background is black
    Mat1b bin;
    cvtColor(img, bin, COLOR_BGR2GRAY);
    bin = bin < 200;

    // Find all white pixels
    vector<Point> pts;
    findNonZero(bin, pts);

    // Get rotated rect of white pixels
    RotatedRect box = minAreaRect(pts);
    if (box.size.width > box.size.height)
    {
        swap(box.size.width, box.size.height);
        box.angle += 90.f;
    }

    Point2f vertices[4];
    box.points(vertices);

    for (int i = 0; i < 4; ++i)
    {
        line(img, vertices[i], vertices[(i + 1) % 4], Scalar(0, 255, 0));
    }

    // Rotate the image according to the found angle
    Mat1b rotated;
    Mat M = getRotationMatrix2D(box.center, box.angle, 1.0);
    warpAffine(bin, rotated, M, bin.size());

    // Compute horizontal projections
    Mat1f horProj;
    reduce(rotated, horProj, 1, CV_REDUCE_AVG);

    // Remove noise in histogram. White bins identify space lines, black bins identify text lines
    float th = 0;
    Mat1b hist = horProj <= th;

    // Get mean coordinate of white white pixels groups
    vector<int> ycoords;
    int y = 0;
    int count = 0;
    bool isSpace = false;
    for (int i = 0; i < rotated.rows; ++i)
    {
        if (!isSpace)
        {
            if (hist(i))
            {
                isSpace = true;
                count = 1;
                y = i;
            }
        }
        else
        {
            if (!hist(i))
            {
                isSpace = false;
                ycoords.push_back(y / count);
            }
            else
            {
                y += i;
                count++;
            }
        }
    }

    // Draw line as final result
    Mat3b result;
    cvtColor(rotated, result, COLOR_GRAY2BGR);
    for (int i = 0; i < ycoords.size(); ++i)
    {
        line(result, Point(0, ycoords[i]), Point(result.cols, ycoords[i]), Scalar(0, 255, 0));
    }

    return 0;
}
 33
Author: Miki,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-24 10:12:55

Podstawowe kroki jako @Miki,

  1. przeczytaj Źródło
  2. threshed
  3. znajdź Minare
  4. warp by the rotated matrix
  5. Znajdź i narysuj górną i dolną granicę]}

Tutaj wpisz opis obrazka


While kod w Pythonie :

#!/usr/bin/python3
# 2018.01.16 01:11:49 CST
# 2018.01.16 01:55:01 CST
import cv2
import numpy as np

## (1) read
img = cv2.imread("img02.jpg")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

## (2) threshold
th, threshed = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY_INV|cv2.THRESH_OTSU)

## (3) minAreaRect on the nozeros
pts = cv2.findNonZero(threshed)
ret = cv2.minAreaRect(pts)

(cx,cy), (w,h), ang = ret
if w>h:
    w,h = h,w
    ang += 90

## (4) Find rotated matrix, do rotation
M = cv2.getRotationMatrix2D((cx,cy), ang, 1.0)
rotated = cv2.warpAffine(threshed, M, (img.shape[1], img.shape[0]))

## (5) find and draw the upper and lower boundary of each lines
hist = cv2.reduce(rotated,1, cv2.REDUCE_AVG).reshape(-1)

th = 2
H,W = img.shape[:2]
uppers = [y for y in range(H-1) if hist[y]<=th and hist[y+1]>th]
lowers = [y for y in range(H-1) if hist[y]>th and hist[y+1]<=th]

rotated = cv2.cvtColor(rotated, cv2.COLOR_GRAY2BGR)
for y in uppers:
    cv2.line(rotated, (0,y), (W, y), (255,0,0), 1)

for y in lowers:
    cv2.line(rotated, (0,y), (W, y), (0,255,0), 1)

cv2.imwrite("result.png", rotated)

Wynik końcowy :

Tutaj wpisz opis obrazka

 9
Author: Silencer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-06-11 09:49:22