Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Image Binarization #201

Open
wants to merge 6 commits into
base: source
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 128 additions & 0 deletions docs/examples/image_binarization/image_binarization.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# ---
# title: Image Binarization
# author: Ashwani Rathee
# date: 2021-7-2
# ---
ashwani-rathee marked this conversation as resolved.
Show resolved Hide resolved

# ImageBinarization.jl provides several algorithms for binarizing images
# into background and foreground(bi-level image). In this demonstration,
# we'll be exploring where these algorithms could be useful and learn how
# different algorithms are suited for different types of tasks.

# Suppose a person wants to make an automatic Sudoku solver using Computer
# Vision . Before anything else that person needs to import the image,
# preprocess it to optimize it for cell content extraction which can then
# be used for solving the sudoku.

# Let's first import packages and then testimage sudoku

using Images, ImageBinarization, TestImages
using CoordinateTransformations, Rotations, ImageTransformations

# Original Image

img = testimage("sudoku")

# Grayscale version as binarize! only accepts Grayscale images

img = Gray.(img)
ashwani-rathee marked this conversation as resolved.
Show resolved Hide resolved

# Let's first rotate our image

trfm = recenter(RotMatrix(pi / 30), center(img));
imgw = warp(img, trfm)

# Now let's zoom in a bit

imgw = parent(imgw)[65:490, 65:490]
ashwani-rathee marked this conversation as resolved.
Show resolved Hide resolved

# Now let's binarize an image using the `Sauvola` algorithm

alg = Sauvola();
img_otsu = binarize(imgw, alg)
mosaicview(imgw, img_otsu; ncol = 2)

# Now the differences between the binarized and non-binarized images
# become apparent. Let's now implement a function to see how different algorithms
# perform for this particular problem.

algs = [
"AdaptiveThreshold",
"Balanced",
"Entropy",
"Intermodes",
"MinimumError",
"Moments",
"Niblack",
"Otsu",
"Polysegment",
"Sauvola",
"UnimodalRosin",
"Yen",
]
function binarize_methods(img_input, algs)
imgs_binarized = Array[]
for i in algs
alg = getfield(ImageBinarization, Symbol(i))()
img_input1 = binarize(img_input, alg)
push!(imgs_binarized, img_input1)
end
return imgs_binarized
end
Comment on lines +49 to +71
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the advantage of this compared to

algs = [AdaptiveThreshold(), Balanced(), Entropy(), Intermodes(),
        MinimumError(), Moments(), Niblack(), Otsu(),
        Polysegment(), Sauvola(), UnimodalRosin(), Yen()]
outs = map(alg->binarize(img, alg), algs)

?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't know we could make array of functions like this. Suggested version would be more concise then..


output = binarize_methods(imgw, algs);


for i = 1:12 #src
save("assets/test$(i).png", RGB.(output[i])) #src
end #src

# | Algorithm | Algorithm | Algorithm |
# | :------: | :------: | :------: |
# | AdaptiveThreshold | Balanced | Entropy |
# | ![](assets/test1.png) | ![](assets/test2.png) | ![](assets/test3.png) |
# | MinimumError | Moments | Niblack |
# | ![](assets/test5.png) | ![](assets/test6.png) | ![](assets/test7.png) |
# | Polysegment | Sauvola | UnimodalRosin |
# | ![](assets/test9.png) | ![](assets/test10.png) | ![](assets/test11.png) |
# | Intermodes | Otsu | Yen |
# | ![](assets/test4.png) | ![](assets/test8.png) | ![](assets/test12.png) |

# We can choose one of methods based on the results here, and use `OCReact.jl`
# which is based on Tesseract OCR to find the content in the sudoku and then
# solve the sudoku.
Comment on lines +91 to +93
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't find OCReact.jl in JuliaHub and GitHub.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should use https://github.com/pixel27/Tesseract.jl instead because it's backed by Tesseract_jll

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OCReract.jl is better maintained I think, no updates in Tesseract.jl in last 15 months. But https://github.com/JuliaPackaging/Yggdrasil shows that Tesseract folder was updated in past 12 days. I don't know enough binary builds, so we can go for Tesseract.jl if you think that's a better option

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OCReract.jl is better maintained I think

Nope, OCReract.jl requires the user to manually install Tesseract and make it available in PATH. While Tesseract.jl ships a prebuilt Tesseract using BinaryBuilder so that user just need pkg> add Tesseract and that's all.


# Now let's take a simple example to understand the behavior of these algorithms
#

img = Array(reshape(range(0, stop = 1, length = 4 * 10^4), 200, 200))
img = Gray.(img)

# Implementation of algorithms on this example

output = binarize_methods(img, algs);

for i = 1:12 #src
save("assets/test$(i+12).png", RGB.(output[i])) #src
end #src

# | Algorithm | Algorithm | Algorithm |
# | :------: | :------: | :------: |
# | AdaptiveThreshold | Balanced | Entropy |
# | ![](assets/test13.png) | ![](assets/test14.png) | ![](assets/test15.png) |
# | MinimumError | Moments | Niblack |
# | ![](assets/test16.png) | ![](assets/test17.png) | ![](assets/test18.png) |
# | Polysegment | Sauvola | UnimodalRosin |
# | ![](assets/test19.png) | ![](assets/test20.png) | ![](assets/test21.png) |
# | Intermodes | Otsu | Yen |
# | ![](assets/test22.png) | ![](assets/test23.png) | ![](assets/test24.png) |

# For every pixel, the same threshold value is applied. In some cases, if the pixel value is smaller
# than the threshold, it is set to 0, otherwise it is set to a maximum value. In other algorithms,
# a complete opposite approach is used.

# Every algorithm has its own way of selecting that threshold and in some methods
# it can be defined.

# `binarize_methods()`` could be particularly useful when you are not aware of the
# underlying assumptions made in algorithms and see all results and choose between them.