5

For my project i'm trying to binarize an image with openCV in python. I used the adaptive gaussian thresholding from openCV to convert the image with the following result: enter image description here

I want to use the binary image for OCR but it's too noisy. Is there any way to remove the noise from the binary image in python? I already tried fastNlMeansDenoising from openCV but it doesn't make a difference.

P.S better options for binarization are welcome as well

R.hagens
  • 325
  • 4
  • 19

3 Answers3

3

It is also possible using GraphCuts for this kind of task. You will need to install the maxflow library in order to run the code. I quickly copied the code from their tutorial and modified it, so you could run it more easily. Just play around with the smoothing parameter to increase or decrease the denoising of the image.

import cv2
import numpy as np
import matplotlib.pyplot as plt
import maxflow 

# Important parameter
# Higher values means making the image smoother
smoothing = 110

# Load the image and convert it to grayscale image 
image_path = 'your_image.png'
img = cv2.imread('image_path')
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
img = 255 * (img > 128).astype(np.uint8)

# Create the graph.
g = maxflow.Graph[int]()
# Add the nodes. nodeids has the identifiers of the nodes in the grid.
nodeids = g.add_grid_nodes(img.shape)
# Add non-terminal edges with the same capacity.
g.add_grid_edges(nodeids, smoothing)
# Add the terminal edges. The image pixels are the capacities
# of the edges from the source node. The inverted image pixels
# are the capacities of the edges to the sink node.
g.add_grid_tedges(nodeids, img, 255-img)

# Find the maximum flow.
g.maxflow()
# Get the segments of the nodes in the grid.
sgm = g.get_grid_segments(nodeids)

# The labels should be 1 where sgm is False and 0 otherwise.
img_denoised = np.logical_not(sgm).astype(np.uint8) * 255

# Show the result.
plt.subplot(121)
plt.imshow(img, cmap='gray')
plt.title('Binary image')
plt.subplot(122)
plt.title('Denoised binary image')
plt.imshow(img_denoised, cmap='gray')
plt.show()

# Save denoised image
cv2.imwrite('img_denoised.png', img_denoised)

Result

p13rr0m
  • 1,107
  • 9
  • 21
2

You should start by adjusting the parameters to the adaptive threshold so it uses a larger area. That way it won't be segmenting out noise. Whenever your output image has more noise than the input image, you know you're doing something wrong.

I suggest as an adaptive threshold to use a closing (on the input grey-value image) with a structuring element just large enough to remove all the text. The difference between this result and the input image is exactly all the text. You can then apply a regular threshold to this difference.

Cris Luengo
  • 55,762
  • 10
  • 62
  • 120
  • I'm already using the adaptive thresholding, but I tried playing around with the variables which made a huge difference. Thanks for your answer – R.hagens Mar 19 '18 at 08:06
0

You could try the morphological transformation close to remove small "holes". First define a kernel using numpy, you might need to play around with the size. Choose the size of the kernel as big as your noise.

kernel = np.ones((5,5),np.uint8)

Then run the morphologyEx using the kernel.

denoised = cv2.morphologyEx(image, cv2.MORPH_CLOSE, kernel)

If text gets removed you can try to erode the image, this will "grow" the black pixels. If the noise is as big as the data, this method will not help.

erosion = cv2.erode(img,kernel,iterations = 1)
aclown
  • 101
  • 4
  • 1
    Thanks for your answer, I like your approach but it's not really working for this image. But I already found another method to binarize the image named Sauvola threshold – R.hagens Mar 16 '18 at 12:13
  • 1
    Thanks for your comment. I was afraid this could not work, the image is pretty noisy. Sauvola threshold looks promising, i will try that too! – aclown Mar 16 '18 at 12:19