<jupyter_start><jupyter_text>Morphology (Tutorial 3)*** Table of Contents1.   [Setup](Setup)2.   [Exercise 1 - Connected Components](Exercise-1---Connected-Components)3.   [Exercise 2 - Dilation](Exercise-2---Dilation)4.   [Exercise 3 - Erosion](Exercise-3---Erosion)5.   [Exercise 4 - Opening](Exercise-4---Opening)6.   [Exercise 5 - Closing](Exercise-5---Closing)7.   [Exercise 6 - Line Segmentation](Exercise-6---Line-Segmentation)  SetupThese libraries are needed for this project:* opencv (cv2) - For image processing* numpy - For its arrays* matplotlib - Plotting histograms* os - File traversal* tqdm.notebook - tqdm progress bars, but for ipynb files* Classes - Custom classes written by me for this assignmentI am using python 3.8 and will be providing an html file generated by jubyter notebooks because I think its morepresentable than just the ipynb file.<jupyter_code>%%capture

import cv2
import numpy as np
from matplotlib import pyplot as plt
import os
from tqdm.notebook import tqdm
# Load the images into memory
shapes = cv2.imread('Images/shapes.jpg')
euro = cv2.imread('Images/Euro_Coins.jpg')
text = cv2.imread('Images/text.png')<jupyter_output><empty_output><jupyter_text>Exercise 1 - Connected Components getObjectsFor this exercise I wrote the getObjects function. In the function I first apply a grayscale filter to the input image,followed by a gaussian blur. This blur smoothens the objects edges making it easier to get them labelled.The next step is to get the object labels using the connectedComponents function. Then I colour the components todifferentiate between the objects.<jupyter_code>def getObjects(image, title):
    # gray scale
    gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
    # blur using gaussian to help reduce noise
    dst = cv2.GaussianBlur(gray,(13,13),cv2.BORDER_DEFAULT)
    # treshold image
    ret, thresh = cv2.threshold(dst,230,255,cv2.THRESH_BINARY_INV)
    # save treshold
    cv2.imwrite("Output/threshold "+title+".png", thresh, [cv2.IMWRITE_PNG_COMPRESSION, 0])

    # get labels
    ret, labels = cv2.connectedComponents(thresh)

    # Colour the labels
    label_hue = np.uint8(179 * labels / np.max(labels))
    blank_ch = 255 * np.ones_like(label_hue)
    labeled_img = cv2.merge([label_hue, blank_ch, blank_ch])
    labeled_img = cv2.cvtColor(labeled_img, cv2.COLOR_HSV2BGR)
    labeled_img[label_hue == 0] = 0
    # save labels as image
    cv2.imwrite("Output/"+title+str(ret-1)+".png", labeled_img, [cv2.IMWRITE_PNG_COMPRESSION, 0])

getObjects(shapes.copy(), "shapes")
getObjects(euro.copy(), "euro")<jupyter_output><empty_output><jupyter_text>Here I can see that the white mask of the objects came out very clean with no noise inside the objects. This in turngave a clean input to connectedComponents which labeled each object perfectly.Input | Threshold | Labeled-|-|- |  |  |  |  getROIWith getROI I am able to get a ROI of each object in the image. Similar to getObjects, the input image goes through agrayscale, gaussian and threshold filter. Then by using the findContours function I am able to get split the input imageinto various ROIs by creating a bounding box with each contour's dimensions.<jupyter_code>def getROI(image, title):
    # gray scale
    gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
    # blur using gaussian to help reduce noise
    dst = cv2.GaussianBlur(gray,(13,13),cv2.BORDER_DEFAULT)
    # treshold image
    ret, thresh = cv2.threshold(dst,230,255,cv2.THRESH_BINARY_INV)
    # getting ROIs with findContours
    contours = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[0]
    i = 1
    for cnt in contours:
        (x,y,w,h) = cv2.boundingRect(cnt)
        ROI = image[y:y+h,x:x+w]
        cv2.imwrite("Output/"+title+" object "+str(i)+".png", ROI, [cv2.IMWRITE_PNG_COMPRESSION, 0])
        i+=1

getROI(shapes.copy(), "shapes")
getROI(euro.copy(), "euro")<jupyter_output><empty_output><jupyter_text>The getRoi function is able to pick each object out.When passed the shapes.jpg, we get these 6 ROIs, each showing a shape:And when passed the Euro_Coins.jpg, we get these 8 ROIs, each showing a coin:  Exercise 2 - DilationFor this exercise I dilate the text.png image using two different kernels too see their results.<jupyter_code>%%capture
kernel = np.ones((3, 3), 'uint8')
dilate = cv2.dilate(text.copy(), kernel)
cv2.imwrite("Output/dilated text 3x3.png", dilate, [cv2.IMWRITE_PNG_COMPRESSION, 0])
kernel = np.ones((5, 5), 'uint8')
dilate = cv2.dilate(text.copy(), kernel)
cv2.imwrite("Output/dilated text 5x5.png", dilate, [cv2.IMWRITE_PNG_COMPRESSION, 0])<jupyter_output><empty_output><jupyter_text>Input | 3x3 | 5x5-|-|- |  | As the kernel increases the dilate effect is increased as it impacts a larger area. With the 5x5 kernel, the text is nolonger readable.  Exercise 3 - ErosionFor this exercise I erode the text.png image using two different kernels too see their results.<jupyter_code>%%capture
kernel = np.ones((3, 3), 'uint8')
erode = cv2.erode(text.copy(), kernel)
cv2.imwrite("Output/eroded text 3x3.png", erode, [cv2.IMWRITE_PNG_COMPRESSION, 0])
kernel = np.ones((5, 5), 'uint8')
erode = cv2.erode(text.copy(), kernel)
cv2.imwrite("Output/eroded text 5x5.png", erode, [cv2.IMWRITE_PNG_COMPRESSION, 0])<jupyter_output><empty_output><jupyter_text>Input | 3x3 | 5x5-|-|- |  | As the kernel increases the erode effect is increased as it impacts a larger area. Both kernels bolded the text howeverwith the 5x5 kernel this effect is almost too much.  Exercise 4 - OpeningFor this exercise I open the text.png image using two different kernels too see their results.<jupyter_code>%%capture
kernel = np.ones((3, 3), 'uint8')
open = cv2.morphologyEx(text.copy(), cv2.MORPH_OPEN ,kernel)
cv2.imwrite("Output/open text 3x3.png", open, [cv2.IMWRITE_PNG_COMPRESSION, 0])
kernel = np.ones((5, 5), 'uint8')
open = cv2.morphologyEx(text.copy(), cv2.MORPH_OPEN ,kernel)
cv2.imwrite("Output/open text 5x5.png", open, [cv2.IMWRITE_PNG_COMPRESSION, 0])<jupyter_output><empty_output><jupyter_text>Input | 3x3 | 5x5-|-|- |  | I saw that using opening barely affected the text as with both kernels the text is still very readable. However I noticedthat the letters became a bit smudged, and I think this would be useful to create a mask of the letter objects.  Exercise 5 - ClosingFor this exercise I close the text.png image using two different kernels too see their results.<jupyter_code>%%capture
kernel = np.ones((3, 3), 'uint8')
close = cv2.morphologyEx(text.copy(), cv2.MORPH_CLOSE ,kernel)
cv2.imwrite("Output/close text 3x3.png", close, [cv2.IMWRITE_PNG_COMPRESSION, 0])
kernel = np.ones((5, 5), 'uint8')
close = cv2.morphologyEx(text.copy(), cv2.MORPH_CLOSE ,kernel)
cv2.imwrite("Output/close text 5x5.png", close, [cv2.IMWRITE_PNG_COMPRESSION, 0])<jupyter_output><empty_output><jupyter_text>Input | 3x3 | 5x5-|-|- |  | Input | 3x3 | 5x5-|-|- |  | I saw that this kernel's output was similar to the dilate output. However as is explained in the notes the erode stepthat is done after the dilate step as the barely visible letters get back some of their colour.  Exercise 6 - Line SegmentationFor this exercise I decided to not use histograms because I felt that for this case, I did not need to use them. Havingnow learnt the application of the above techniques I recognised that by applying a pseudo close I would be able toextract 4 rectangles that cover the words in the text. I say pseudo since the dilation and erosion applied will havedifferent kernels passed.<jupyter_code>%%capture
# Get the grayscale
gray = cv2.cvtColor(text.copy(),cv2.COLOR_BGR2GRAY)
# Apply the gaussian filter because of the noise
dst = cv2.GaussianBlur(gray, (13,13), cv2.BORDER_DEFAULT)
# Apply a high threshold
ret, thresh = cv2.threshold(dst, 187, 255, cv2.THRESH_BINARY)
# Dilate with a wide kernel
kernel = np.ones((5, 70), 'uint8')
dilate = cv2.morphologyEx(thresh, cv2.MORPH_DILATE ,kernel)
# Now apply erosion with a very wide kernel, and as high as it can get before making lines meet
kernel = np.ones((25, 130), 'uint8')
erode = cv2.morphologyEx(dilate, cv2.MORPH_ERODE ,kernel)
# Flip white and black
final = cv2.bitwise_not(erode)
cv2.imwrite("Output/LineThresh.png", final, [cv2.IMWRITE_PNG_COMPRESSION, 0])<jupyter_output><empty_output><jupyter_text>Input | Line Mask -|- |  Now that I have successfully approximated where the lines are, I will use the connectedComponents function and thecolouring done previously to create a colour mask of these 4 lines.<jupyter_code>%%capture
# get labels
ret, labels = cv2.connectedComponents(final)

# Colour the labels
label_hue = np.uint8(179 * labels / np.max(labels))
blank_ch = 255 * np.ones_like(label_hue)
labeled_img = cv2.merge([label_hue, blank_ch, blank_ch])
labeled_img = cv2.cvtColor(labeled_img, cv2.COLOR_HSV2BGR)
labeled_img[label_hue == 0] = 0
# save labels as image
cv2.imwrite("Output/LabelledLines.png", labeled_img, [cv2.IMWRITE_PNG_COMPRESSION, 0])<jupyter_output><empty_output><jupyter_text>Input | Labeled Line Mask -|- |  Now that each line is very clearly distinguishable I will use the coloured mask to paint over the text with their newcolours.I will achieve this by following these steps:* Apply a 3x3 kernel closure on the image    * This step is done to remove a bunch of noise from the grayed out text image that would otherwise be enhanced by        the next step.* Do the reverse of a gaussian filter and sharpen the image* Erode the image with a 3x3 kernel* Apply a 0 threshold* Flip black and whiteThis will result with a white mask of the letters.<jupyter_code>%%capture
gray = cv2.cvtColor(text.copy(),cv2.COLOR_BGR2GRAY)
kernel = np.ones((5, 5), 'uint8')
close = cv2.morphologyEx(gray, cv2.MORPH_CLOSE ,kernel)
kernel = np.array([[-1,-1,-1], [-1,9,-1], [-1,-1,-1]])
im = cv2.filter2D(close, -1, kernel)
kernel = np.ones((3, 3), 'uint8')
erode = cv2.morphologyEx(im, cv2.MORPH_ERODE ,kernel)
ret, thresh = cv2.threshold(erode, 0, 235, cv2.THRESH_BINARY)
txt_white = cv2.bitwise_not(thresh)
cv2.imwrite("Output/TextLettersMask.png", thresh, [cv2.IMWRITE_PNG_COMPRESSION, 0])<jupyter_output><empty_output><jupyter_text>Input | Letter Mask -|- |  Now the only thing that is left is to go over the labeled image (the one with the coloured lines). In the loop I use theenumerate function which allows me to check the txt_white image (the one shown above). When the checked pixel is changedto that of the labelled image. This results in the text getting a bit bold and getting some new colour, where the abovecode identified a line.<jupyter_code>%%capture
c_text = text.copy()
for i, y in enumerate(labeled_img):
    for j, x in enumerate(y):
        if np.sum(txt_white[i][j]) == 255:
            c_text[i][j] = x

cv2.imwrite("Output/FinalColouredText.png", c_text, [cv2.IMWRITE_PNG_COMPRESSION, 0])<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.10524372230428361, 'fraction_numerical': 0.027695716395864108, 'mean_word_length': 4.890701468189233, 'pattern_counts': {'":': 0, '<': 45, '<?xml version=': 0, '>': 45, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '11', 'dir': 'jupyter-structured-clean-dedup', 'id': '289391', 'license': 'MIT', 'n_tokens_mistral': 3821, 'n_tokens_neox': 3617, 'n_words': 1186, 'path': 'Tutorial.ipynb', 'repo_name': 'AidenWilliams/Tutorial-3', 'size': '10832'}