{"id":1087,"date":"2022-06-07T15:24:21","date_gmt":"2022-06-07T10:54:21","guid":{"rendered":"https:\/\/m-shaeri.ir\/blog\/?p=1087"},"modified":"2024-09-06T10:17:23","modified_gmt":"2024-09-06T06:47:23","slug":"scanned-document-image-preprocessing-for-machine-learning-classification-feature-extraction","status":"publish","type":"post","link":"https:\/\/mshaeri.com\/blog\/scanned-document-image-preprocessing-for-machine-learning-classification-feature-extraction\/","title":{"rendered":"Scanned Document Preprocessing For Classification and Feature Extraction"},"content":{"rendered":"\n<p>These days document images feature extraction and classification are highly demanded tasks in companies and organizations. The image can be a digital document or scanned paper. Feature extraction is the task of extracting information from document image. Whereas the classification is the process of classifying documents based on their text contents and\/or their structural properties. <\/p>\n\n\n\n<p>Working with digital document images is much easier than dealing with scanned documents\/papers, because first ones are mostly trim and neat, yet the scanned documents are often noisy, crooked and angled\/skewed. In this post I will share my experience from my recent project in which I had to extract information from around 2,000,000 scanned papers. I focus on tree important tasks, <a href=\"#docuemnt-image-denoising\" data-type=\"internal\" data-id=\"#docuemnt-image-denoising\"><strong>denoising<\/strong> <\/a>, <a href=\"#document-image-binarization\" data-type=\"internal\" data-id=\"#document-image-binarization\"><strong>binarization<\/strong> <\/a>and <strong><span style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-green-cyan-color\"><a href=\"#document-image-rotate-align\" data-type=\"internal\" data-id=\"#document-image-rotate-align\">aligning the skewed document image<\/a><\/span><\/strong>.  I use Python and OpenCV to work on images.<\/p>\n\n\n\n<h2 class=\"wp-block-heading has-vivid-cyan-blue-color has-text-color has-medium-font-size\" id=\"docuemnt-image-denoising\">Denoising the document image<\/h2>\n\n\n\n<p class=\"has-black-color has-text-color\">Removing noise from scanned paper is a necessary task before applying machine learning algorithms. There exist several supervised\/unsupervised denoising methods. In this post, we use non-local means method to eliminate noises from image. It simply replaces the color of a pixel with an average of the colors of similar pixels. But the most similar pixels to a given pixel have no reason to be close at all (<a href=\"http:\/\/www.ipol.im\/pub\/algo\/bcm_non_local_means_denoising\"><span class=\"has-inline-color has-vivid-cyan-blue-color\">paper link<\/span><\/a>). OpenCV <span class=\"has-inline-color has-vivid-red-color\"><strong>fastNlMeansDenoising()<\/strong><\/span> function removes noises using non-local means denoising algorithm with some computational optimizations.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"python\" class=\"language-python\">import cv2\nimport numpy as np\n\n#read the noisy image\nnoisyImage= cv2.imread(\"noisy_image.jpg\",cv2.IMREAD_GRAYSCALE)\n#applying fast non-local means denoisong filter\ndenoisedImage= cv2.fastNlMeansDenoising(noisyImage, None, h = 44, templateWindowSize &nbsp;= 7, searchWindowSize = 21)\n#join noisy and denoised images\nnosiy_denoised = np.concatenate((noisyImage, denoisedImage), axis=1)\n#save joined images in file\ncv2.imwrite(\"nosiy_denoised.jpg\",nosiy_denoised)<\/code><\/pre>\n\n\n\n<p>In the <strong>fastNlMeanDenoising <\/strong>function we need to specify following parameters :<\/p>\n\n\n\n<p> <strong>templateWindowSize <\/strong>: It is  size of the template patch which is used to compute weights. Should be odd. Recommended value for better denoising performance is 7<\/p>\n\n\n\n<p><strong>searchWindowSize <\/strong>: It is size of the window that is used to compute weighted average for<br>given pixel. Should be odd.  The greater value leads to longer denoising time. Recommended value for best performance is 21.<\/p>\n\n\n\n<p><strong>h<\/strong>  : This parameter regulates filter strength. A big h value perfectly removes noise but also has side effects on image details, whereas smaller h value preserves details but also preserves some noise.<\/p>\n\n\n\n<p>Output of above code on a noisy image :<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/m-shaeri.ir\/blog\/wp-content\/uploads\/2022\/06\/nosiy_denoised-scaled.jpg\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"660\" src=\"https:\/\/m-shaeri.ir\/blog\/wp-content\/uploads\/2022\/06\/nosiy_denoised-1024x660.jpg\" alt=\"image denoise\" class=\"wp-image-1115\" srcset=\"https:\/\/mshaeri.com\/blog\/wp-content\/uploads\/2022\/06\/nosiy_denoised-1024x660.jpg 1024w, https:\/\/mshaeri.com\/blog\/wp-content\/uploads\/2022\/06\/nosiy_denoised-300x193.jpg 300w, https:\/\/mshaeri.com\/blog\/wp-content\/uploads\/2022\/06\/nosiy_denoised-768x495.jpg 768w, https:\/\/mshaeri.com\/blog\/wp-content\/uploads\/2022\/06\/nosiy_denoised-1536x990.jpg 1536w, https:\/\/mshaeri.com\/blog\/wp-content\/uploads\/2022\/06\/nosiy_denoised-2048x1320.jpg 2048w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/a><figcaption class=\"wp-element-caption\">scanned document denoising<\/figcaption><\/figure>\n\n\n\n<h2 class=\"wp-block-heading is-style-default has-vivid-cyan-blue-color has-text-color has-medium-font-size\" id=\"document-image-binarization\"><span style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-cyan-blue-color\">Scanned Document Binarization <\/span><\/h2>\n\n\n\n<p>Binarization is a crucial task that should be done before feature extraction, it converts an image into black and white image in which white pixels are represented by 255 and black pixels by 0. We do the binarization using a threshold. If in the given image a pixel value exceeds the threshold, we set it as a white pixel with value of 255, otherwise we set the pixel as black with value of zero. If we choose a good threshold, it can also help in noise reduction. So, choosing appropriate <strong><em>threshold<\/em><\/strong> is the most important part of binarizatio.  OTSU\u2019s method calculates a threshold for the whole image considering the several characteristics of the entire image. <span class=\"has-inline-color has-vivid-red-color\">When we use OTSU method, we don&#8217;t need to determine threshold explicitly, so the threshold function will ignore the 0 and 255 in the  argument.<\/span><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"python\" class=\"language-python\">import cv2\nimport numpy as np\n\n#read image from file\nimg = cv2.imread(\"test.jpg\",cv2.IMREAD_GRAYSCALE)\n\n# binarization with OTSU threshold finder. 0 and 255 are ignored\nthreshValue, binaryImage = cv2.threshold(img, 0, 255, cv2.THRESH_OTSU)\n\nnormal_binary = np.concatenate((img, binaryImage), axis=1)\n\ncv2.imwrite(\"normal_binary.jpg\",normal_binary)<\/code><\/pre>\n\n\n\n<p>Ouput of above code on our scanned document :<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/m-shaeri.ir\/blog\/wp-content\/uploads\/2022\/06\/normal_binary.jpg\"><img decoding=\"async\" src=\"https:\/\/m-shaeri.ir\/blog\/wp-content\/uploads\/2022\/06\/normal_binary-1024x679.jpg\" alt=\"scanned document binarization using otsu method\" class=\"wp-image-1144\"\/><\/a><figcaption class=\"wp-element-caption\">scanned document binarization using otsu method<\/figcaption><\/figure>\n\n\n\n<h2 class=\"wp-block-heading is-style-default has-vivid-cyan-blue-color has-text-color has-medium-font-size\" id=\"document-image-rotate-align\">Aligning Scanned document<\/h2>\n\n\n\n<p>Skewed scanned document is a common issue in feature extraction and also image classification tasks. To solve this problem by re-aligning the document image, first we need to find the deviation angle of the content against the horizontal line. Then, we can rotate the image in the opposite direction of deviation to align the document. To find the deviation angle of the content against the horizontal line, we have to extract content&#8217;s lines, we do it using <strong>Canny <\/strong>edge detection<strong> <\/strong>function along with  <strong>HoughLinesP <\/strong>line detection function. When we have the widest line of the document we can find the angle between it and the horizontal line, we wrote <strong>get_angle <\/strong>function to do this task for us. Finally, we rotate the image content to remove deviation, it is done by <strong>rotate_image <\/strong>function in the code below :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"python\" class=\"language-python\">\nimport cv2\nimport numpy as np\nimport math\n\ndef get_angle(x1, y1, x2, y2) -&gt; float:\n&nbsp; &nbsp; \"\"\"Get the angle of this line with the horizontal axis.\"\"\"\n&nbsp; &nbsp; deltaX = x2 - x1\n&nbsp; &nbsp; deltaY = y2 - y1\n&nbsp; &nbsp; angleInDegrees = np.arctan2(deltaY , deltaX) * 180 \/ math.pi\n&nbsp; &nbsp; \n&nbsp; &nbsp; return angleInDegrees\n\ndef rotate_image(image, angle):\n&nbsp; &nbsp; image_center = tuple(np.array(image.shape[1::-1]) \/ 2)\n&nbsp; &nbsp; rot_mat = cv2.getRotationMatrix2D(image_center, angle, 1.0)\n&nbsp; &nbsp; result = cv2.warpAffine(image, rot_mat, image.shape[1::-1], flags=cv2.INTER_LINEAR, borderValue=(255,255,255) )\n&nbsp; &nbsp; return result\n\n\ndef align_image(img):\n\n&nbsp; &nbsp; # Median blurring to get rid of the noise; invert image\n&nbsp; &nbsp; #img = &nbsp;cv2.medianBlur(img, 3) # use this if the document image is noisy\n\n&nbsp; &nbsp; edges = cv2.Canny(img, 80, 120)\n\n&nbsp; &nbsp; # Detect and draw lines\n&nbsp; &nbsp; lines = cv2.HoughLinesP(edges, 1, np.pi\/180, 10, minLineLength=20, maxLineGap=10)\n    # sort lines from widest to shortest\n&nbsp; &nbsp; lines = sorted(lines,key = (lambda l: abs(l[0][0]-l[0][2])) , reverse = True)\n\n    # if there exist any line, compare it by horizontal line\n    # and rotate the image if the angle difference is more than 0.25\n&nbsp; &nbsp; for line in lines:\n&nbsp; &nbsp; &nbsp; &nbsp; for x1, y1, x2, y2 in line:\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (abs(x2-x1) \/ edges.shape[1])&gt;0.25 :\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; angle = get_angle(x1, y1, x2, y2)\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if abs(angle) &gt; 1.0 :\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; img = rotate_image(img,angle)\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; print(\"rotated\")\n        #exit after comparing widest line\n&nbsp; &nbsp; &nbsp; &nbsp; break\n\n&nbsp; &nbsp; return img\n\n#read the unligned image\nunalignedImage= cv2.imread(\"unaligned_image.jpg\",cv2.IMREAD_GRAYSCALE)\n\n#apply re-aligning function\naligned_image = align_image(unalignedImage)\n\nunaligned_aligned = np.concatenate((unalignedImage, aligned_image), axis=1)\n#save joined images in file\ncv2.imwrite(\"unaligned_aligned.jpg\",unaligned_aligned)\n<\/code><\/pre>\n\n\n\n<p>Output of the <strong>align_image <\/strong>function :<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/m-shaeri.ir\/blog\/wp-content\/uploads\/2022\/06\/unaligned_aligned.jpg\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"647\" src=\"https:\/\/m-shaeri.ir\/blog\/wp-content\/uploads\/2022\/06\/unaligned_aligned-1024x647.jpg\" alt=\"image alignment\" class=\"wp-image-1131\" srcset=\"https:\/\/mshaeri.com\/blog\/wp-content\/uploads\/2022\/06\/unaligned_aligned-1024x647.jpg 1024w, https:\/\/mshaeri.com\/blog\/wp-content\/uploads\/2022\/06\/unaligned_aligned-300x189.jpg 300w, https:\/\/mshaeri.com\/blog\/wp-content\/uploads\/2022\/06\/unaligned_aligned-768x485.jpg 768w, https:\/\/mshaeri.com\/blog\/wp-content\/uploads\/2022\/06\/unaligned_aligned-1536x970.jpg 1536w, https:\/\/mshaeri.com\/blog\/wp-content\/uploads\/2022\/06\/unaligned_aligned.jpg 1558w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/a><figcaption class=\"wp-element-caption\"><strong>Left : Before re-aligning the document , Right : after re-alignment<\/strong><\/figcaption><\/figure>\n\n\n\n<p>You can find the snippet code working on a sample noisy document on <a href=\"https:\/\/github.com\/birddevelper\/ScannedDocumentPreprocessing\" data-type=\"URL\" data-id=\"https:\/\/github.com\/birddevelper\/ScannedDocumentPreprocessing\">Github repository<\/a>.<\/p>\n\n\n\n<p>If you have any question on this post, please don&#8217;t hesitate to leave here a comment.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>These days document images feature extraction and classification are highly demanded tasks in companies and organizations. The image can be a digital document or scanned &hellip; <\/p>\n","protected":false},"author":1,"featured_media":1137,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1,148,150,149,151,145,28,147,35,41,146],"tags":[156,157,160,52,167,155,161,154,164,165,159,162,29,152,36,39,158,153,163],"_links":{"self":[{"href":"https:\/\/mshaeri.com\/blog\/wp-json\/wp\/v2\/posts\/1087"}],"collection":[{"href":"https:\/\/mshaeri.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/mshaeri.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/mshaeri.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/mshaeri.com\/blog\/wp-json\/wp\/v2\/comments?post=1087"}],"version-history":[{"count":2,"href":"https:\/\/mshaeri.com\/blog\/wp-json\/wp\/v2\/posts\/1087\/revisions"}],"predecessor-version":[{"id":1863,"href":"https:\/\/mshaeri.com\/blog\/wp-json\/wp\/v2\/posts\/1087\/revisions\/1863"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/mshaeri.com\/blog\/wp-json\/wp\/v2\/media\/1137"}],"wp:attachment":[{"href":"https:\/\/mshaeri.com\/blog\/wp-json\/wp\/v2\/media?parent=1087"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/mshaeri.com\/blog\/wp-json\/wp\/v2\/categories?post=1087"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/mshaeri.com\/blog\/wp-json\/wp\/v2\/tags?post=1087"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}