I'll just write the part where the pre-processing could be improved. Again, the colour of the text is very easy to select and threshold on it with cv2.inRange:

im = cv2.imread("text.png") # read image
lower = (60, 60, 0) # define lower limit
upper = (100, 100, 40) # and upper limit
mask = cv2.inRange(im, lower, upper) # use cv2.inRange
maskClosed = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, np.ones((3,3), dtype = np.uint8)) # use morph close to fill the holes in the mask
plt.imshow(maskClosed) # show mask

The result:

You can use this instead of your current pre-processing part. The letters should be very easily separated from each other.

Answer from Tino D on Stack Overflow
Top answer
1 of 4
21

A great tutorial on the first step you described is available at pyimagesearch (and they have great tutorials in general)

In short, as described by Ella, you would have to use cv2.CHAIN_APPROX_SIMPLE. A slightly more robust method would be to use cv2.RETR_LIST instead of cv2.RETR_EXTERNAL and then sort the areas, as it should decently work even in white backgrounds/if the page inscribes a bigger shape in the background, etc.

Coming to the second part of your question, a good way to segment the characters would be to use the Maximally stable extremal region extractor available in OpenCV. A complete implementation in CPP is available here in a project I was helping out in recently. The Python implementation would go along the lines of (Code below works for OpenCV 3.0+. For the OpenCV 2.x syntax, check it up online)

import cv2

img = cv2.imread('test.jpg')
mser = cv2.MSER_create()

#Resize the image so that MSER can work better
img = cv2.resize(img, (img.shape[1]*2, img.shape[0]*2))

gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
vis = img.copy()

regions = mser.detectRegions(gray)
hulls = [cv2.convexHull(p.reshape(-1, 1, 2)) for p in regions[0]]
cv2.polylines(vis, hulls, 1, (0,255,0)) 

cv2.namedWindow('img', 0)
cv2.imshow('img', vis)
while(cv2.waitKey()!=ord('q')):
    continue
cv2.destroyAllWindows()

This gives the output as

Now, to eliminate the false positives, you can simply cycle through the points in hulls, and calculate the perimeter (sum of distance between all adjacent points in hulls[i], where hulls[i] is a list of all points in one convexHull). If the perimeter is too large, classify it as not a character.

The diagnol lines across the image are coming because the border of the image is black. that can simply be removed by adding the following line as soon as the image is read (below line 7)

img = img[5:-5,5:-5,:]

which gives the output

2 of 4
7

The option on the top of my head requires the extractions of 4 corners of the skewed image. This is done by using cv2.CHAIN_APPROX_SIMPLE instead of cv2.CHAIN_APPROX_NONE when finding contours. Afterwards, you could use cv2.approxPolyDP and hopefully remain with the 4 corners of the receipt (If all your images are like this one then there is no reason why it shouldn't work).

Now use cv2.findHomography and cv2.wardPerspective to rectify the image according to source points which are the 4 points extracted from the skewed image and destination points that should form a rectangle, for example the full image dimensions.

Here you could find code samples and more information: OpenCV-Geometric Transformations of Images

Also this answer may be useful - SO - Detect and fix text skew

EDIT: Corrected the second chain approx to cv2.CHAIN_APPROX_NONE.

🌐
YouTube
youtube.com › watch
Line, word segmentation with OpenCV Python | Tutorial | Computer Vision - YouTube
handwritten text segmentationThis tutorial teaches how you can segment texts and lines, while maintaining order using Python & OpenCVInterested in Computer V...
Published   April 21, 2022
🌐
Medium
sanaazahra.medium.com › ocr-segmentation-with-python-code-f3251114ee48
OCR Segmentation with Python Code | by Sana'a Zahra | Medium
April 29, 2023 - ♦ Character Level Segmentation: will seperate character by character . We need to find the contour lines so we can separate the image line by line from the Background. Finding the contours. horizontal_contours, hierarchy = cv2.findContours(dilation, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE) •cv2.RETR_EXTERNAL= This is a retrieval mode flag that tells OpenCV to only return the outermost (external) contours in the image hierarchy.
🌐
Medium
medium.com › @maniksingh256 › word-segmentation-from-handwritten-paragraphs-using-opencv-tools-6ba05dee13b8
Word Segmentation from Handwritten Paragraphs using OpenCV tools | by Manik Singh | Medium
May 20, 2024 - This is a tutorial on how to use OpenCV to extract separate images of words from an image of handwritten text. The approach followed is based on the method of Blob Detection.
🌐
Medium
arthurflor23.medium.com › text-segmentation-b32503ef2613
Text Segmentation. Document scanner until word… | by Arthur F. S. Neto | Medium
March 2, 2023 - 5), but of course, new processing in python too. To be faster, I changed all preprocessing to c++, and the response was immediate. ... Fig. 5: New binarization test with illumination compensation before · In the end, I chose the Sauvola method with illumination compensation at all. Finally, we arrive at the start point of this project! haha · To have a good orientation of what techniques are present in the middle of text segmentation, I recommend the paper “Text-Based Image Segmentation Methodology”. In it, I was able to find the levels of text segmentation (line, word, character) and methodologies (pixel counting approach, histogram approach, Y histogram projection, text line separation, false line exclusion, line region recovery, smearing approach, stochastic approach, water flow approach), very useful to start.
🌐
IBM
itworkman.com › home › practice of text segmentation with opencv in python
Practice of text segmentation with OpenCV in Python - ITworkman
May 30, 2022 - The area of each row and block is segmented according to the size of the projected area, and the original image is binarized. ... Before projection, adjust the image grayscale, do the expansion operation, select the appropriate core, and expand ...
Top answer
1 of 1
1

hope this helps.

To find each line:

  • Threshold the image with
  • Close the image with custom kernel that only connects vertical blobs (xAxisKernel() is the custom function i wrote to create the kernel that has 1 in its center and neighbouring rows, 0 elswhere )
  • Find external contours which will be the contours of each line

This is the thresholded image:

This is the closed image with 17 kernel size, now all lines are connected as one big blob:

This is the bounding boxes for each contour which are all detected lines:

Now repeating the same procedure for detecting each word in one line:

  • Cut the line portion of image
  • Threshold the cut portion (or just cut the thresholded verison)
  • Close the image with custom kernel that only connects vertical blobs
  • Find external contours which will be the contours of each word

This the thresholded line image:

This is the closed image with 7 kernel size, smaller kernel because we just want to connect the letters of a word instead of connecting each word together:

This is the bounding boxes for each contour which are all detected lines:

This is the final result of word detection:

From this point you can use your own code that detects each letter of a word for every detected word.

This is the complete code:

import cv2
import numpy

#Funciton to create custom kernel
def xAxisKernel(size):
    size = size if size%2 else size+1
    xkernel = numpy.zeros((size,size),dtype=numpy.uint8)
    center = size//2
    for j in range(size):
        xkernel[center][j] = 1
        xkernel[center-1][j] = 1
        xkernel[center+1][j] = 1
    return xkernel

#Give filtered image, get bounding boxes for the lines
def findLines(full_image):

    #Threshold the image, close with custom kernel to connect only vertical blobs which are letters
    gray = cv2.cvtColor(full_image,cv2.COLOR_BGR2GRAY)
    _,thresh = cv2.threshold(gray,200,255,cv2.THRESH_BINARY_INV)
    closed = cv2.morphologyEx(thresh,cv2.MORPH_CLOSE,xAxisKernel(17)) #Connecting every word of a line together
    
    #Find line contours in the closed image
    contours,hierarchy = cv2.findContours(closed,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
    lines = []
    for cnt in contours:
        if cv2.contourArea(cnt)>100: #to eliminate the dots
            x,y,w,h = cv2.boundingRect(cnt)
            lines.append([x,y,w,h])
    return lines


#Give the cropped image of a line, get bounding boxes for the words 
def findWords(line_image):

    #Add white border around the line for better filtering
    line_image = cv2.copyMakeBorder(line_image,top=10,bottom=10,left=10,right=10,borderType=cv2.BORDER_CONSTANT,value=[255, 255, 255])

    #Threshold the image, close with custom kernel to connect only vertical blobs which are letters
    gray = cv2.cvtColor(line_image,cv2.COLOR_BGR2GRAY)
    _,thresh = cv2.threshold(gray,200,255,cv2.THRESH_BINARY_INV)
    closed = cv2.morphologyEx(thresh,cv2.MORPH_CLOSE,xAxisKernel(7)) #Connecting the letters of a word

    #Find word contours in the closed image
    contours,hierarchy = cv2.findContours(closed,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
    words = []
    for cnt in contours:
        if cv2.contourArea(cnt)>100:#to eliminate the dots
            x,y,w,h = cv2.boundingRect(cnt)
            words.append([x-10,y-10,w,h]) #subtracting the added border size for correction
            cv2.rectangle(line_image,(x,y),(x+w,y+h),(0,0,255),2)

    return words


#Read the image
image = cv2.imread('ph1.png')
height,width,channel = image.shape

# Find all lines in the image
al_lines = findLines(image)

# Iterate on each line and find all words
for line in al_lines:
    lineX,lineY,lineW,lineH = line #Line bounding box coordinates
    words = findWords(image[lineY:lineY+lineH,lineX:lineX+lineW])
    
    for word in words:
        x,y,w,h = word #Word bounding box coordinates
        
        # Add line start position as offset to word start position for correction
        x = x+lineX
        y = y+lineY
        #Draw bounding box around each word
        cv2.rectangle(image,(x,y),(x+w,y+h),(0,0,255),2)

cv2.imshow('Original',image)
cv2.waitKey(0)
Find elsewhere
🌐
GitHub
github.com › AswinVishnuA › Character-Segmentation-using-OpenCV
GitHub - AswinVishnuA/Character-Segmentation-using-OpenCV: For a given image with text in white background generates line,word and character segments and stores in seperate folders.
This project focuses on image segmentation using the OpenCV library in Python. Image segmentation is the process of partitioning an image into meaningful segments or regions to simplify the representation and make it easier to analyze.
Author   AswinVishnuA
🌐
GitHub
github.com › arthurflor23 › text-segmentation › blob › master › README.md
text-segmentation/README.md at master · arthurflor23/text-segmentation
A simple pre-project in python with the handwritten text segmentation module in c++. GCC/G++ 8+ Python 3.7 · openCV 3+ python main.py -c -p · or · python3 main.py -c -p · Specify an image · python main.py -c -p --image xxx.png · or · python3 main.py -c -p --image xxx.png ·
Author   arthurflor23
🌐
OpenCV Q&A Forum
answers.opencv.org › question › 104136 › line-segmentation-in-handwritten-text
Line segmentation in handwritten text - OpenCV Q&A Forum
October 9, 2016 - I'm developing a simple script for extracting features of each of the lines of a image that contains handwritten text. After thresholding the image I add to the Numpy matrix a complete white row and complete black row (first two rows). I want to calculate pairwise the cosine similarity of the ...
🌐
Stack Exchange
datascience.stackexchange.com › questions › 116292 › tensorflow-text-segmentation-or-open-cv-text-segmenation
python - Tensorflow Text Segmentation or Open CV Text Segmenation - Data Science Stack Exchange
November 18, 2022 - I tried solution 1 from Kaggle https://www.kaggle.com/code/irinaabdullaeva/text-segmentation not getting desired result · I tried solution 2 from kaggle https://www.kaggle.com/code/dmitryyemelyanov/receipt-ocr-part-1-image-segmentation-by-opencv not getting desired result
🌐
DEV Community
dev.to › video › introduction-to-text-detection-using-opencv-and-pytesseract-3he2
Introduction to text detection using OpenCV and pytesseract - DEV Community
March 2, 2021 - Detecting and reading text from images is a complex task with a multitude of factors that is needed to take into account, it can be something like detecting handwritten text on a piece of paper to detect subtitles in a movie. In this short tutorial we are going to utilise OpenCVs image manipulation tools and the Python wrapper for Google’s Tesseract-OCR Engine Python-tesseract or pytesseract to detect a specific number of predefined words in a video sequence.
🌐
Stack Overflow
stackoverflow.com › questions › 45966425 › improving-ocr-text-detection-segmentation-in-natural-images-using-opencv
python - improving OCR text detection/segmentation in natural images using OpenCV - Stack Overflow
August 30, 2017 - */ #include "opencv2/text.hpp" #include "opencv2/core/utility.hpp" #include "opencv2/core/core.hpp" #include "opencv2/highgui/highgui.hpp" #include "opencv2/imgproc/imgproc.hpp" #include <fstream> #include <iostream> using namespace std; using namespace cv; using namespace cv::text; //Calculate edit distance between two words size_t edit_distance(const string& A, const string& B); size_t min(size_t x, size_t y, size_t z); bool isRepetitive(const string& s); bool sort_by_lenght(const string &a, const string &b); //Draw ER's in an image via floodFill void er_draw(vector<Mat> &channels, vector<ve
🌐
O'Reilly
oreilly.com › library › view › learn-opencv-4 › 9781789341225 › 2ed3a033-477f-48f7-9084-bf5d531dee8e.xhtml
Text segmentation - Learn OpenCV 4 by Building Projects - Second Edition [Book]
November 30, 2018 - Use classifiers to search for a previously trained letter texture pattern: with texture features such as Haralick features, wavelet transforms are often used. Anther option is to identify maximally stable extremal regions (MSERs) in this task.
Authors   David Millán EscriváVinícius G. Mendonça
Published   2018
Pages   310
🌐
Kaggle
kaggle.com › code › dmitryyemelyanov › receipt-ocr-part-1-image-segmentation-by-opencv
Receipt OCR Part 1: Image segmentation by OpenCV
Checking your browser before accessing www.kaggle.com · Click here if you are not automatically redirected after 5 seconds