i am new to OpenCV and following this tutorial here is the Python code
from imutils import paths
import numpy as np
import imutils
import cv2
import os
rectKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (13, 5))
sqKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (21, 21))
image = cv2.imread("src/main/resources/passport/passport.jpg")
image = imutils.resize(image, height=600)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
gray = cv2.GaussianBlur(gray, (3, 3), 0)
blackhat = cv2.morphologyEx(gray, cv2.MORPH_BLACKHAT, rectKernel)
gradX = cv2.Sobel(blackhat, ddepth=cv2.CV_32F, dx=1, dy=0, ksize=-1)
gradX = np.absolute(gradX)
(minVal, maxVal) = (np.min(gradX), np.max(gradX))
gradX = (255 * ((gradX - minVal) / (maxVal - minVal))).astype("uint8")
gradX = cv2.morphologyEx(gradX, cv2.MORPH_CLOSE, rectKernel)
thresh = cv2.threshold(gradX, 0, 255, cv2.THRESH_BINARY cv2.THRESH_OTSU)[1]
thresh = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, sqKernel)
thresh = cv2.erode(thresh, None, iterations=4)
p = int(image.shape[1] * 0.05)
thresh[:, 0:p] = 0
thresh[:, image.shape[1] - p:] = 0
cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
cnts = sorted(cnts, key=cv2.contourArea, reverse=True)
for c in cnts:
(x, y, w, h) = cv2.boundingRect(c)
ar = w / float(h)
crWidth = w / float(gray.shape[1])
if ar > 5 and crWidth > 0.75:
pX = int((x + w) * 0.03)
pY = int((y + h) * 0.03)
(x, y) = (x - pX, y - pY)
(w, h) = (w + (pX * 2), h + (pY * 2))
roi = image[y:y + h, x:x + w].copy()
cv2.rectangle(image, (x, y), (x + w, y + h), (0, 255, 0), 2)
break
cv2.imshow("Image", image)
cv2.imshow("ROI", roi)
cv2.imwrite("src/main/resources/passport/mrz.jpg", roi)
and here is the converted scala code
System.loadLibrary(Core.NATIVE_LIBRARY_NAME)
// Initialize a rectangular and square structuring kernel
val rectKernel = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(25, 7))
val sqKernel = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(21, 21))
val imagePath = config.getString("passport.path")
val image = Imgcodecs.imread(imagePath)
val resizedImage = new Mat()
val targetHeight = 600
val aspectRatio = image.width().toDouble / image.height()
val targetWidth = Math.round(targetHeight * aspectRatio)
Imgproc.resize(image, resizedImage, new Size(targetWidth, targetHeight), 0, 0, Imgproc.INTER_AREA)
val grayScale = new Mat()
Imgproc.cvtColor(resizedImage, grayScale, Imgproc.COLOR_BGR2GRAY)
val grayBlurred = new Mat()
Imgproc.GaussianBlur(grayScale, grayBlurred, new Size(3, 3), 0)
val blackhat = new Mat()
Imgproc.morphologyEx(grayBlurred, blackhat, Imgproc.MORPH_BLACKHAT, rectKernel)
val blacHatImagePath = "src/main/resources/ocr/passport/passport-blackhat.jpeg"
Imgcodecs.imwrite(blacHatImagePath, blackhat)
val gradX = new Mat()
Imgproc.Sobel(blackhat, gradX, CvType.CV_32F, 1, 0, -1) // Example kernel size
Core.absdiff(gradX, new Scalar(0), gradX) // Calculate absolute values
val minVal = Core.minMaxLoc(gradX).minVal
val maxVal = Core.minMaxLoc(gradX).maxVal
Core.normalize(gradX, gradX, 0, 255, Core.NORM_MINMAX, CvType.CV_8U)
Imgproc.morphologyEx(gradX, gradX, Imgproc.MORPH_CLOSE, rectKernel) // Apply closing operation
val thresh = new Mat()
Imgproc.threshold(gradX, thresh, 0, 255, Imgproc.THRESH_BINARY | Imgproc.THRESH_OTSU) // Apply Otsu's thresholding
Imgproc.morphologyEx(thresh, thresh, Imgproc.MORPH_CLOSE, sqKernel)
Imgproc.erode(thresh, thresh, new Mat(), new Point(-1, -1), 4)
val numColumnsToZero = (resizedImage.cols() * 0.05).toInt
val leftRect = new Rect(0, 0, numColumnsToZero, resizedImage.rows())
val rightRect = new Rect(resizedImage.cols() - numColumnsToZero, 0, numColumnsToZero, resizedImage.rows())
Imgproc.rectangle(thresh, leftRect.tl(), leftRect.br(), Scalar.all(0), Core.FILLED)
Imgproc.rectangle(thresh, rightRect.tl(), rightRect.br(), Scalar.all(0), Core.FILLED)
val borderImagePath = "src/main/resources/ocr/passport/passport-thresh.jpeg"
Imgcodecs.imwrite(borderImagePath, thresh)
val contours = new ArrayBuffer[MatOfPoint]() // Use ArrayBuffer to store contours
Imgproc.findContours(thresh.clone(), contours.asJava, new Mat(), Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE)
val sortedContours = contours.sortBy(-Imgproc.contourArea(_)) // Scala's pattern matching and '-' for descending order
var breakLoop = false
for (c <- sortedContours if !breakLoop) {
val rect = Imgproc.boundingRect(c)
var x = rect.x
var y = rect.y
var w = rect.width
var h = rect.height
// Calculate the aspect ratio and coverage ratio
val aspectRatio = w.toDouble / h
val coverageRatio = w.toDouble / grayScale.cols() // Assuming gray is the original image
var roi = new Mat()
if (aspectRatio > 5 && coverageRatio > 0.75) {
val paddingX = ((x + w)* 0.03).toInt
val paddingY = ((y + h)* 0.03).toInt
x = x - paddingX
y = y - paddingY
w = w + (paddingX
h = h + (paddingY * 2)
// Extract ROI and draw a rectangle
roi = image.submat(y, y + h, x, x + w)
Imgproc.rectangle(resizedImage, new Point(x, y), new Point(x + w, y + h), new Scalar(0, 255, 0), 2)
breakLoop = true
}
val roiImagePath = "src/main/resources/ocr/passport/passport-mrz-detected.jpeg"
Imgcodecs.imwrite(roiImagePath, roi)
}
i am taking the same image but both are printing different sorted contours values also, the Python code detects the mrz region correctly and the Scala code detects the incorrect part of an image
here are the output values of the Python code
sorted contours is [array([[[ 22, 563]],
[[ 22, 583]],
[[372, 583]],
[[372, 582]],
[[373, 581]],
[[373, 565]],
[[219, 565]],
[[217, 563]]], dtype=int32), array([[[192, 412]],
[[191, 413]],
[[156, 413]],
[[156, 450]],
[[157, 451]],
[[157, 501]],
[[158, 502]],
[[163, 502]],
[[164, 503]],
[[164, 518]],
[[165, 519]],
[[171, 519]],
[[172, 520]],
[[172, 523]],
[[217, 523]],
[[217, 479]],
[[218, 478]],
[[218, 458]],
[[219, 457]],
[[232, 457]],
[[232, 456]],
[[217, 456]],
[[216, 455]],
[[216, 413]],
[[217, 412]],
[[247, 412]]], dtype=int32), array([[[204, 252]],
[[204, 265]],
[[357, 265]],
[[358, 264]],
[[386, 264]],
[[387, 265]],
[[409, 265]],
[[409, 252]],
[[395, 252]],
[[394, 253]],
[[321, 253]],
[[320, 252]]], dtype=int32), array([[[ 83, 252]],
[[ 82, 253]],
[[ 60, 253]],
[[ 59, 254]],
[[ 59, 255]],
[[ 58, 256]],
[[ 58, 262]],
[[ 60, 264]],
[[ 61, 264]],
[[ 62, 265]],
[[166, 265]],
[[167, 264]],
[[166, 263]],
[[166, 253]],
[[152, 253]],
[[151, 252]]], dtype=int32), array([[[369, 460]],
[[369, 481]],
[[368, 482]],
[[352, 482]],
[[351, 483]],
[[349, 483]],
[[349, 495]],
[[405, 495]],
[[405, 488]],
[[404, 487]],
[[404, 485]],
[[397, 485]],
[[396, 484]],
[[396, 474]],
[[371, 474]],
[[370, 473]],
[[370, 463]],
[[369, 462]]], dtype=int32), array([[[335, 323]],
[[335, 324]],
[[334, 325]],
[[319, 325]],
[[319, 330]],
[[333, 330]],
[[334, 331]],
[[334, 352]],
[[335, 353]],
[[389, 353]],
[[389, 351]],
[[348, 351]],
[[347, 350]],
[[347, 323]]], dtype=int32), array([[[ 81, 500]],
[[ 81, 507]],
[[ 82, 508]],
[[ 82, 520]],
[[ 86, 520]],
[[ 86, 505]],
[[ 85, 504]],
[[ 85, 503]],
[[ 84, 502]],
[[ 84, 501]],
[[ 83, 500]]], dtype=int32), array([[[ 85, 355]],
[[ 85, 356]],
[[ 97, 356]],
[[ 97, 355]]], dtype=int32), array([[[ 61, 353]],
[[ 61, 354]],
[[ 68, 354]],
[[ 68, 353]]], dtype=int32), array([[[108, 427]],
[[108, 428]],
[[109, 428]],
[[110, 427]]], dtype=int32), array([[[ 51, 429]],
[[ 53, 429]]], dtype=int32), array([[[178, 326]],
[[178, 331]]], dtype=int32)]
p is 22
gardx is (600, 447)
minVal is 0.0
maxVal is 2469.0
pX is 11
py is 17
x is 11
y is 546
w is 374
h is 55
ar is 16.761904761904763
crWidth is 0.7874720357941835
c is (8, 1)
roi is [[[255 250 248]
[255 254 246]
[254 253 245]
...
[248 244 245]
[246 245 245]
[248 248 248]]
[[254 252 249]
[255 252 248]
[254 251 247]
...
[252 245 242]
[252 246 243]
[253 248 245]]
[[250 250 248]
[255 252 250]
[255 251 249]
...
[255 250 246]
[254 249 244]
[254 248 243]]
...
[[255 255 255]
[255 255 255]
[254 254 254]
...
[252 249 244]
[253 249 244]
[253 250 244]]
[[255 255 255]
[255 255 255]
[254 254 254]
...
[251 251 245]
[251 252 246]
[251 252 246]]
[[255 255 255]
[255 255 255]
[254 254 254]
...
[250 252 246]
[250 252 246]
[251 253 247]]]
and here are Scala code output values
info] orignal image rows 805 and column 600
[info] orignal image width 600 and height 805
[info] resized image rows 600 and column 447
[info] resized image width 447 and height 600
[info] minVal is 0.0
[info] maxVal is 2469.0
[info] grad x is 447x600
[info] value of p is 22
[info] sorted contours ArrayBuffer(Mat [ 9*1*CV_32SC2, isCont=true, isSubmat=false, nativeObj=0x7f84a0ead8b0, dataAddr=0x7f84a0eadac0 ], Mat [ 31*1*CV_32SC2, isCont=true, isSubmat=false, nativeObj=0x7f84a0e6c870, dataAddr=0x7f84a0bfd500 ], Mat [ 14*1*CV_32SC2, isCont=true, isSubmat=false, nativeObj=0x7f84a0e6ca30, dataAddr=0x7f84a0e6bf00 ], Mat [ 12*1*CV_32SC2, isCont=true, isSubmat=false, nativeObj=0x7f84a0e6caa0, dataAddr=0x7f84a0e6c040 ], Mat [ 16*1*CV_32SC2, isCont=true, isSubmat=false, nativeObj=0x7f84a0e6d670, dataAddr=0x7f84a0e6ba40 ], Mat [ 9*1*CV_32SC2, isCont=true, isSubmat=false, nativeObj=0x7f84a0e6d590, dataAddr=0x7f84a0e6b900 ], Mat [ 5*1*CV_32SC2, isCont=true, isSubmat=false, nativeObj=0x7f84a0e6c9c0, dataAddr=0x7f84a0ead540 ], Mat [ 8*1*CV_32SC2, isCont=true, isSubmat=false, nativeObj=0x7f84a0e6c800, dataAddr=0x7f84a0e6bb80 ], Mat [ 2*1*CV_32SC2, isCont=true, isSubmat=false, nativeObj=0x7f84a0e6d600, dataAddr=0x7f84a0e6b980 ], Mat [ 2*1*CV_32SC2, isCont=true, isSubmat=false, nativeObj=0x7f84a0e6c8e0, dataAddr=0x7f84a0e6bd00 ], Mat [ 2*1*CV_32SC2, isCont=true, isSubmat=false, nativeObj=0x7f84a0e6c950, dataAddr=0x7f84a0e6bdc0 ])
[info] c is Mat [ 9*1*CV_32SC2, isCont=true, isSubmat=false, nativeObj=0x7f84a0ead8b0, dataAddr=0x7f84a0eadac0 ]
[info] in if block
[info] px is 11
[info] py is 17
[info] x is 12
[info] y is 546
[info] w is 373
[info] h is 55
[info] aspectRatio is 16.714285714285715
[info] coverageRatio is 0.785234899328859
[info] c is1x9
[info] original image is empty false
[info] new mrz image is empty false
[info] roi is Mat [ 55*373*CV_8UC3, isCont=false, isSubmat=true, nativeObj=0x7f84a0e9a950, dataAddr=0x7f845814ef74 ]
I was unable to write the same code in Scala as given in Python
cnts = imutils.grab_contours(cnts)
cnts = sorted(cnts, key=cv2.contourArea, reverse=True)
I have added a workaround for that maybe that is causing the issue, please help
- the first image is the original image
- the second image is the output of Python image which I desire in Scala
- the third image is the output of the scala code which is incorrect


