diff --git a/assets/Music-selected.svg b/assets/Music-selected.svg new file mode 100644 index 0000000..ff3d7a2 --- /dev/null +++ b/assets/Music-selected.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/assets/arrow-down 1.svg b/assets/arrow-down 1.svg new file mode 100644 index 0000000..15adf7a --- /dev/null +++ b/assets/arrow-down 1.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/arrow-up 1.svg b/assets/arrow-up 1.svg new file mode 100644 index 0000000..5c1e308 --- /dev/null +++ b/assets/arrow-up 1.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/logo-dynamsoft-blackBg-190x47.png b/assets/logo-dynamsoft-blackBg-190x47.png new file mode 100644 index 0000000..7967e8e Binary files /dev/null and b/assets/logo-dynamsoft-blackBg-190x47.png differ diff --git a/assets/music-unselected.svg b/assets/music-unselected.svg new file mode 100644 index 0000000..4b5f4be --- /dev/null +++ b/assets/music-unselected.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/assets/passport frame.svg b/assets/passport frame.svg new file mode 100644 index 0000000..caa616c --- /dev/null +++ b/assets/passport frame.svg @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/css/index.css b/css/index.css new file mode 100644 index 0000000..77bbbe9 --- /dev/null +++ b/css/index.css @@ -0,0 +1,265 @@ +@font-face { + font-family: "Oswald-Regular"; + src: url("../font/Oswald-Regular.ttf"); +} + +@font-face { + font-family: "Oswald-Light"; + src: url("../font/Oswald-Light.ttf"); +} + +@font-face { + font-family: "OpenSans-Regular"; + src: url("../font/OpenSans-Regular.ttf"); +} + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +html, +body { + width: 100%; + height: 100%; + -webkit-text-size-adjust: 100%; + /* Prevent font scaling in landscape while allowing user zoom */ +} + +.home-page { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + z-index: 1; + display: flex; + flex-direction: column; + justify-content: space-between; + align-items: center; + color: #ffffff; + background-color: #323234; + padding: 30px 0; +} + +.home-page .logo { + width: 150px; + height: 36px; +} + +.home-page .description { + width: 80%; + text-align: center; +} + +.home-page .description .title { + font-size: 30px; + font-family: Oswald-Regular; +} + +.home-page .description .content { + font-family: OpenSans-Regular; + font-size: 16px; + line-height: 26px; + margin: 16px 0 25px 0; +} + +.home-page .description .start-btn { + width: 180px; + height: 6vh; + min-height: 40px; + max-height: 60px; + background-color: #FE8E14; + font-family: Oswald-Regular; + font-size: 20px; + margin: 0 auto; + display: flex; + justify-content: center; + align-items: center; +} + +.home-page .power { + font-size: 16px; + font-family: Oswald-Light; +} + +.scanner-container { + position: absolute; + width: 100%; + height: 100%; + display: none; +} + +.scanner-container .header { + width: 100%; + height: 46px; + background-color: rgb(55, 55, 55); + display: flex; + align-items: center; +} + +.scanner-container .header .camera-selector { + width: 70px; + height: 100%; + background-color: #000000; + display: flex; + justify-content: space-around; + align-items: center; + padding: 0 10px; + margin-right: 15px; +} + +.scanner-container .header .camera-selector .camera-svg { + width: 28px; + height: 16px; +} + +.scanner-container .header .camera-selector .up { + display: none; +} + +.scanner-container .header .camera-list { + width: 165px; + position: absolute; + top: 46px; + left: 0; + background-color: #000000; + z-index: 1; + display: none; +} + +.scanner-container .header .camera-list .camera-item { + width: 100%; + height: 40px; + color: #AAAAAA; + border-bottom: 1px solid rgb(50, 50, 52); + font-size: 12px; + font-family: "OpenSans-Regular"; + line-height: 40px; + padding: 0 10px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} + +.scanner-container .header .camera-list .camera-selected { + color: #fe8e14; +} + +.scanner-container .header .camera-list .camera-item:last-child { + border: unset; +} + +.scanner-container .header .music { + width: 30px; + height: 30px; +} + +.scanner-container .header .no-music { + width: 22px; + height: 22px; + display: none; +} + +.result-container { + position: absolute; + width: 100%; + height: 100%; + display: none; + flex-direction: column; + justify-content: space-between; + background-color: #323234; + color: #ffffff; + font-family: "OpenSans-Regular"; + font-size: 16px; + z-index: 2; +} + +.result-container .result-header { + width: 100%; + height: 6%; + min-height: 35px; + max-height: 50px; + padding: 0 15px 0 30px; + background-color: #2B2B2B; + display: flex; + justify-content: space-between; + align-items: center; +} + +.result-container .result-header .result-title { + color: #AAAAAA; +} + +.result-container .result-header .result-restart { + color: #FE8E14; +} + +.result-container .parsed-result-area { + width: 100%; + height: 84%; + padding: 15px 15px 5px 30px; + overflow: auto; +} + +.result-container .parsed-result-area .parsed-result-header { + font-size: 18px; + margin-bottom: 30px; +} + +.result-container .parsed-result-area .parsed-filed { + display: flex; + flex-direction: column; + margin-bottom: 25px; +} + +.result-container .parsed-result-area .parsed-filed .field-name { + color: #AAAAAA; +} + +.result-container .restart-video { + width: 100%; + height: 10%; + min-height: 60px; + max-height: 100px; + display: flex; + justify-content: center; + align-items: center; +} + +.result-container .restart-video .btn-restart-video { + width: 160px; + height: 60%; + border: 0; + background-color: #fe8e14; + font-size: 20px; + color: #ffffff; + font-family: "Oswald-Regular"; +} + +@keyframes dce-rotate { + from { + transform: rotate(0turn); + } + + to { + transform: rotate(1turn); + } +} + +@keyframes dce-scanlight { + from { + top: 0; + } + + to { + top: 97%; + } +} + +@media screen and (max-width: 780px) and (orientation: landscape) { + .result-container .parsed-result-area .parsed-filed { + font-size: 14px; + } +} \ No newline at end of file diff --git a/font/OpenSans-Regular.ttf b/font/OpenSans-Regular.ttf new file mode 100644 index 0000000..2e31d02 Binary files /dev/null and b/font/OpenSans-Regular.ttf differ diff --git a/font/Oswald-Light.ttf b/font/Oswald-Light.ttf new file mode 100644 index 0000000..dcfaa60 Binary files /dev/null and b/font/Oswald-Light.ttf differ diff --git a/font/Oswald-Regular.ttf b/font/Oswald-Regular.ttf new file mode 100644 index 0000000..2492c44 Binary files /dev/null and b/font/Oswald-Regular.ttf differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..69b2520 --- /dev/null +++ b/index.html @@ -0,0 +1,160 @@ + + + + + + + Passport MRZ Scanner + + + + + + + + + + + +
+ +
+
Passport Scanner
+
Dynamsoft Passport Scanner recognizes the Machine-Readable Zone (MRZ) on the biographical page of a passport and converts the encoded strings into human-readable fields.
+
Scan a Passport
+
+
Powered by Dynamsoft
+
+
+
+
+ + + + + + + + + + + down + up +
+
+ music + no-music +
+
+ + + + +
+
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
Passport Scan Results:
+
Restart >
+
+
+
+
+
+
+
+
+
+ +
+
+ + + + + + \ No newline at end of file diff --git a/js/define.js b/js/define.js new file mode 100644 index 0000000..b234a6c --- /dev/null +++ b/js/define.js @@ -0,0 +1,29 @@ +// Define some global variables that need to be used +let cameraView; +let cvRouter = null; +let cameraEnhancer = null; +let cameraList = []; +let promiseCVRReady = null; +let isPlaySound = true; +let timer; + +// Get the ui element +const homePage = document.querySelector(".home-page"); +const scannerContainer = document.querySelector(".scanner-container"); +const startScaningBtn = document.querySelector(".start-btn"); +const resultRestartBtn = document.querySelector(".result-restart"); +const passportFrame = document.querySelector(".passport-frame"); +const restartVideoBtn = document.querySelector(".btn-restart-video"); +const resultContainer = document.querySelector(".result-container"); +const cameraViewContainer = document.querySelector(".div-ui-container"); +const parsedResultArea = document.querySelector(".parsed-result-area"); +const parsedResultHeader = document.querySelector(".parsed-result-header"); +const parsedResultName = document.querySelector(".name"); +const parsedResultSexAndAge = document.querySelector(".sex-and-age"); +const parsedResultMain = document.querySelector(".parsed-result-main"); +const cameraListDiv = document.querySelector(".camera-list") +const cameraSelector = document.querySelector(".camera-selector"); +const playSoundBtn = document.querySelector(".music"); +const closeSoundBtn = document.querySelector(".no-music"); +const down = document.querySelector(".down"); +const up = document.querySelector(".up"); \ No newline at end of file diff --git a/js/index.js b/js/index.js new file mode 100644 index 0000000..877379f --- /dev/null +++ b/js/index.js @@ -0,0 +1,152 @@ +import { pDataLoad, cvrReady } from "./init.js"; +import { checkOrientation, getVisibleRegionOfVideo } from "./util.js" + +async function startCapturing() { + try { + await (promiseCVRReady = promiseCVRReady || (async () => { + homePage.style.display = "none"; + scannerContainer.style.display = "block"; + + // After the model and wasm are loaded, turn on the camera + await cvrReady; + await pDataLoad.promise; + + // Starts streaming the video + await cameraEnhancer.open(); + const currentCamera = cameraEnhancer.getSelectedCamera(); + for (let child of cameraListDiv.childNodes) { + if (currentCamera.deviceId === child.deviceId) { + child.className = "camera-item camera-selected"; + } + } + passportFrame.style.display = "inline-block"; + cameraEnhancer.setScanRegion(region()); + // cameraView.setScanRegionMaskStyle({ + // lineWidth: 2, + // strokeStyle: "rgb(254,142,20)", + // fillStyle: "rgba(0,0,0,0.5)", + // }); + cameraView.setScanRegionMaskVisible(false); + await cvRouter.startCapturing("ReadPassport"); + })()); + } catch (ex) { + let errMsg = ex.message || ex; + console.error(errMsg); + alert(errMsg); + } +} + +// -----------Logic code for calculating scan region ↓------------ +const regionEdgeLength = () => { + if (!cameraEnhancer || !cameraEnhancer.isOpen()) return 0; + const visibleRegionInPixels = getVisibleRegionOfVideo(); + const visibleRegionWidth = visibleRegionInPixels.width; + const visibleRegionHeight = visibleRegionInPixels.height; + const regionEdgeLength = 0.4 * Math.min(visibleRegionWidth, visibleRegionHeight); + return Math.round(regionEdgeLength); +}; + +const regionLeft = () => { + if (!cameraEnhancer || !cameraEnhancer.isOpen()) return 0; + const visibleRegionInPixels = getVisibleRegionOfVideo(); + const currentResolution = cameraEnhancer.getResolution(); + let vw = currentResolution.width; + if (checkOrientation() === "portrait") { + vw = Math.min(currentResolution.width, currentResolution.height); + } else { + vw = Math.max(currentResolution.width, currentResolution.height); + } + const visibleRegionWidth = visibleRegionInPixels.width; + let left = 0.5 - regionEdgeLength() / vw / 2; + let regionCssW; + if (document.body.clientWidth > document.body.clientHeight * 6.73) { + let regionCssH = document.body.clientHeight * 0.75; + regionCssW = regionCssH * 6.73; + } else { + regionCssW = document.body.clientWidth * 0.9; + } + regionCssW = Math.min(regionCssW, 600); + const regionWidthInPixel = (visibleRegionWidth / document.body.clientWidth) * regionCssW; + left = ((vw - regionWidthInPixel) / 2 / vw) * 100; + left = Math.round(left); + return left; +}; + +const regionTop = () => { + if (!cameraEnhancer || !cameraEnhancer.isOpen()) return 0; + const currentResolution = cameraEnhancer.getResolution(); + let vw = currentResolution.width; + let vh = currentResolution.height; + if (checkOrientation() === "portrait") { + vw = Math.min(currentResolution.width, currentResolution.height); + vh = Math.max(currentResolution.width, currentResolution.height); + } else { + vw = Math.max(currentResolution.width, currentResolution.height); + vh = Math.min(currentResolution.width, currentResolution.height); + } + let top = 0.5 - regionEdgeLength() / vh / 2; + const regionWidthInPixel = vw - (regionLeft() * 2 * vw) / 100; + const regionHeightInPixel = regionWidthInPixel / 4; // 6.73 is the aspect ratio of 'passportf frame.svg' // todo + top = ((vh - regionHeightInPixel) / 2 / vh) * 100; + top = Math.round(top); + return top; +}; + +const region = () => { + let region = { + left: regionLeft(), + right: 100 - regionLeft(), + top: regionTop(), + bottom: 100 - regionTop(), + isMeasuredInPercentage: true, + }; + return region; +} +// -----------Logic code for calculating scan region ↑------------ + +const restartVideo = async () => { + resultContainer.style.display = "none"; + await cvRouter.startCapturing("ReadPassport"); +} + +window.addEventListener("click", () => { + cameraListDiv.style.display = "none"; + up.style.display = "none"; + down.style.display = "inline-block"; +}) + +// Recalculate the scan region when the window size changes +window.addEventListener("resize", () => { + passportFrame.style.display = "none"; + timer && clearTimeout(timer); + timer = setTimeout(() => { + passportFrame.style.display = "inline-block"; + cameraEnhancer.setScanRegion(region()); + cameraView.setScanRegionMaskVisible(false); + }, 500); +}) + +// Add click events to some buttons +startScaningBtn.addEventListener("click", startCapturing); +restartVideoBtn.addEventListener("click", restartVideo); +resultRestartBtn.addEventListener("click", restartVideo); + +cameraSelector.addEventListener("click", (e) => { + e.stopPropagation(); + const isShow = cameraListDiv.style.display === "block"; + cameraListDiv.style.display = isShow ? "none" : "block"; + up.style.display = !isShow ? "inline-block" : "none"; + down.style.display = isShow ? "inline-block" : "none"; +}) + +playSoundBtn.addEventListener("click", () => { + playSoundBtn.style.display = "none"; + closeSoundBtn.style.display = "block"; + isPlaySound = false; +}) + +closeSoundBtn.addEventListener("click", () => { + playSoundBtn.style.display = "block"; + closeSoundBtn.style.display = "none"; + isPlaySound = true; +}) \ No newline at end of file diff --git a/js/init.js b/js/init.js new file mode 100644 index 0000000..22476f9 --- /dev/null +++ b/js/init.js @@ -0,0 +1,116 @@ + +import { createPendingPromise, getNeedShowFields } from "./util.js"; + +// Promise variable used to control model loading state +let pDataLoad = createPendingPromise(); + +/** LICENSE ALERT - README + * To use the library, you need to first specify a license key using the API "license" as shown below. + */ +// Dynamsoft.License.LicenseManager.initLicense("DLS2eyJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSJ9"); +Dynamsoft.License.LicenseManager.initLicense("DLS2eyJoYW5kc2hha2VDb2RlIjoiODAwMC04MDAwIiwibWFpblNlcnZlclVSTCI6Imh0dHBzOi8vMTkyLjE2OC44LjEyMi9kbHMvIiwib3JnYW5pemF0aW9uSUQiOiI4MDAwIiwiY2hlY2tDb2RlIjotMTA1NjUzOTM3M30=", true); +/** + * You can visit https://www.dynamsoft.com/customer/license/trialLicense?utm_source=github&product=dlr&package=js to get your own trial license good for 30 days. + * Note that if you downloaded this sample from Dynamsoft while logged in, the above license key may already be your own 30-day trial license. + * For more information, see https://www.dynamsoft.com/label-recognition/programming/javascript/user-guide.html?ver=latest#specify-the-license or contact support@dynamsoft.com. + * LICENSE ALERT - THE END + */ + +Dynamsoft.DLR.LabelRecognizerModule.onDataLoadProgressChanged = (modelPath, tag, progress) => { + if (tag === "completed") { + pDataLoad.resolve(); + } +} + +/** + * Preloads the `LabelRecognizer` module + */ +Dynamsoft.Core.CoreModule.loadWasm(["DLR", "DCP"]); +Dynamsoft.DCP.CodeParserModule.loadSpec("MRTD_TD3_PASSPORT"); +Dynamsoft.DLR.LabelRecognizerModule.loadRecognitionData("MRZ"); + +/** + * Creates a CameraEnhancer instance for later use. + */ +async function initDCE() { + cameraView = await Dynamsoft.DCE.CameraView.createInstance(cameraViewContainer); + cameraEnhancer = await Dynamsoft.DCE.CameraEnhancer.createInstance(cameraView); + + // Get the device's camera information and render the camera list + cameraList = await cameraEnhancer.getAllCameras(); + for (let camera of cameraList) { + const cameraItem = document.createElement("div"); + cameraItem.className = "camera-item"; + cameraItem.innerText = camera.label; + cameraItem.deviceId = camera.deviceId; + // + cameraItem.addEventListener("click", (e) => { + e.stopPropagation(); + for (let child of cameraListDiv.childNodes) { + child.className = "camera-item"; + } + cameraItem.className = "camera-item camera-selected"; + cameraEnhancer.selectCamera(camera); + }) + cameraListDiv.appendChild(cameraItem); + } + cameraView.setVideoFit("cover"); +} + +/** + * Creates a CaptureVisionRouter instance and configure the task to recognize text. + * Also, make sure the original image is returned after it has been processed. + */ +let cvrReady = (async function initCVR() { + await initDCE(); + cvRouter = await Dynamsoft.CVR.CaptureVisionRouter.createInstance(); + await cvRouter.initSettings("./template.json"); + cvRouter.setInput(cameraEnhancer); + + /* Defines the result receiver for the task.*/ + const resultReceiver = new Dynamsoft.CVR.CapturedResultReceiver(); + resultReceiver.onCapturedResultReceived = (result) => { + const recognizedResults = result.textLineResultItems; + const parsedResults = result.parsedResultItems; + + if (recognizedResults) { + parsedResultName.innerText = ""; + parsedResultSexAndAge.innerText = ""; + parsedResultMain.innerText = ""; + // If get parsing result, use the parsing result to render the result page + if (parsedResults) { + const parseResultInfo = getNeedShowFields(parsedResults[0]); + parsedResultName.innerText = parseResultInfo["Name"] || "Name not detected"; + const sex = parseResultInfo["Gender"] || "Sex not detected"; + const age = parseResultInfo["Age"] || "Age not detected"; + parsedResultSexAndAge.innerText = sex + ", Age: " + age; + + for (let field in parseResultInfo) { + if(["Name", "Gender", "Age"].includes(field)) continue; + const p = document.createElement("p"); + p.className = "parsed-filed"; + const spanFieldName = document.createElement("span"); + spanFieldName.className = "field-name"; + const spanValue = document.createElement("span"); + spanValue.className = "field-value"; + spanFieldName.innerText = `${field} : `; + spanValue.innerText = `${parseResultInfo[field] || 'not detected'}`; + p.appendChild(spanFieldName); + p.appendChild(spanValue); + parsedResultMain.appendChild(p); + } + } else { + alert(`Failed to parse the content. The MRZ text ${needShowTextLines}.`); + parsedResultArea.style.justifyContent = "flex-start"; + } + isPlaySound ? Dynamsoft.DCE.Feedback.beep() : null; + resultContainer.style.display = "flex"; + cameraListDiv.style.display = "none"; + cvRouter.stopCapturing(); + cameraView.clearAllInnerDrawingItems(); + } + }; + await cvRouter.addResultReceiver(resultReceiver); +})(); + +export { pDataLoad, cvrReady } \ No newline at end of file diff --git a/js/util.js b/js/util.js new file mode 100644 index 0000000..bd3e6f2 --- /dev/null +++ b/js/util.js @@ -0,0 +1,136 @@ +export function createPendingPromise() { + let resolve, reject; + const promise = new Promise((res, rej) => { + resolve = res; + reject = rej; + }); + + return { promise, resolve, reject }; +} + +export function getNeedShowFields(result) { + const parseResultInfo = {}; + if (!result.exception) { + let name = result.getFieldValue("name"); + parseResultInfo['Name'] = name; + + let gender = result.getFieldValue("sex"); + parseResultInfo["Gender"] = gender; + + let birthYear = result.getFieldValue("birthYear"); + let birthMonth = result.getFieldValue("birthMonth"); + let birthDay = result.getFieldValue("birthDay"); + if (parseInt(birthYear) > (new Date().getFullYear() % 100)) { + birthYear = "19" + birthYear; + } else { + birthYear = "20" + birthYear; + } + if(isNaN(parseInt(birthYear))) { + parseResultInfo["Age"] = undefined; + } else { + let age = new Date().getUTCFullYear() - parseInt(birthYear); + parseResultInfo["Age"] = age; + } + let documentNumber = result.getFieldValue("passportNumber"); + parseResultInfo['Document Number'] = documentNumber; + + let issuingState = result.getFieldValue("issuingState"); + parseResultInfo['Issuing State'] = issuingState; + + let nationality = result.getFieldValue("nationality"); + parseResultInfo['Nationality'] = nationality; + + parseResultInfo['Date of Birth (YYYY-MM-DD)'] = birthYear + "-" + birthMonth + "-" + birthDay; + + let expiryYear = result.getFieldValue("expiryYear"); + let expiryMonth = result.getFieldValue("expiryMonth"); + let expiryDay = result.getFieldValue("expiryDay"); + if (parseInt(expiryYear) >= 60) { + expiryYear = "19" + expiryYear; + } else { + expiryYear = "20" + expiryYear; + } + parseResultInfo["Date of Expiry (YYYY-MM-DD)"] = expiryYear + "-" + expiryMonth + "-" + expiryDay; + + let personalNumber = result.getFieldValue("personalNumber"); + parseResultInfo["Personal Number"] = personalNumber; + + let primaryIdentifier = result.getFieldValue("primaryIdentifier"); + parseResultInfo["Primary Identifier(s)"] = primaryIdentifier; + + let secondaryIdentifier = result.getFieldValue("secondaryIdentifier"); + parseResultInfo["Secondary Identifier(s)"] = secondaryIdentifier; + } + return parseResultInfo; +} + +export function checkOrientation() { + if (window.matchMedia("(orientation: portrait)").matches) { + return 'portrait'; + } else if (window.matchMedia("(orientation: landscape)").matches) { + return 'landscape'; + } +} + +export function getVisibleRegionOfVideo() { + if(!cameraView || !cameraView.getVideoElement()) return; + const video = cameraView.getVideoElement(); + let width = video.videoWidth; + let height = video.videoHeight; + let objectFit = cameraView.getVideoFit(); + + const isPortrait = checkOrientation() === "portrait"; + let _width = width; + let _height = height; + if (isPortrait) { + _width = Math.min(width, height); + _height = Math.max(width, height); + } else { + _width = Math.max(width, height); + _height = Math.min(width, height); + } + width = _width; + height = _height; + + const { width: videoCSSWidth, height: videoCSSHeight } = + cameraView._innerComponent.getBoundingClientRect(); + if (videoCSSWidth <= 0 || videoCSSHeight <= 0) { + throw new Error( + `Unable to get video dimensions. Video may not be rendered on the page.` + ); + } + + const videoCSSWHRatio = videoCSSWidth / videoCSSHeight, + videoWHRatio = width / height; + let cssScaleRatio; + const regionInPixels = { + x: 0, + y: 0, + width: width, + height: height, + isMeasuredInPercentage: false, + }; + + if (objectFit === "cover") { + if (videoCSSWHRatio < videoWHRatio) { + // a part of length is invisible + cssScaleRatio = videoCSSHeight / height; + regionInPixels.x = Math.floor( + (width - videoCSSWidth / cssScaleRatio) / 2 + ); + regionInPixels.y = 0; + regionInPixels.width = Math.ceil(videoCSSWidth / cssScaleRatio); + regionInPixels.height = height; + } else { + // a part of height is invisible + cssScaleRatio = videoCSSWidth / width; + regionInPixels.x = 0; + regionInPixels.y = Math.floor( + (height - videoCSSHeight / cssScaleRatio) / 2 + ); + regionInPixels.width = width; + regionInPixels.height = Math.ceil(videoCSSHeight / cssScaleRatio); + } + } + return regionInPixels; +} \ No newline at end of file diff --git a/template.json b/template.json new file mode 100644 index 0000000..a462ef2 --- /dev/null +++ b/template.json @@ -0,0 +1,118 @@ +{ + "CaptureVisionTemplates" : + [ + { + "Name" : "ReadPassport", + "ImageROIProcessingNameArray" : + [ + "ROI_OriginalImage" + ], + "SemanticProcessingNameArray": [ "SP_Passport" ], + "OutputOriginalImage": 1 + } + ], + "LabelRecognizerTaskSettingOptions": [ + { + "Name": "Task_RecognizeMRZonPassport", + "TextLineSpecificationNameArray": [ + "TLS_Passport" + ], + "SectionImageParameterArray": [ + { + "Section": "ST_TEXT_LINE_LOCALIZATION", + "ImageParameterName": "IP_RecognizePassport" + } + ] + } + ], + "TextLineSpecificationOptions": [ + { + "Name": "TLS_Template", + "CharacterModelName": "MRZ", + "CharHeightRange": [ 5, 1000, 1 ], + "BinarizationModes": [ + { + "BlockSizeX": 30, + "BlockSizeY": 30, + "Mode": "BM_LOCAL_BLOCK", + "MorphOperation": "Close" + } + ] + }, + { + "Name": "TLS_Passport", + "BaseTextLineSpecificationName": "TLS_Template", + "OutputResults": 1, + "ConcatResults": 1, + "SubGroups": [ + { + "StringRegExPattern": "(P[A-Z<][A-Z<]{3}[A-Z<]{39}){(44)}", + "StringLengthRange": [ 44, 44 ], + "BaseTextLineSpecificationName": "TLS_Template", + "TextLinesCount": 1 + }, + { + "StringRegExPattern": "([A-Z0-9<]{9}[0-9][A-Z<]{3}[0-9]{2}[(01-12)][(01-31)][0-9][MF<][0-9]{2}[(01-12)][(01-31)][0-9][A-Z0-9<]{14}[0-9<][0-9]){(44)}", + "StringLengthRange": [ 44, 44 ], + "BaseTextLineSpecificationName": "TLS_Template", + "TextLinesCount": 1 + } + ] + } + ], + "ImageParameterOptions" : + [ + { + "Name" : "IP_RecognizePassport", + "TextureDetectionModes": [ + { + "Mode": "TDM_GENERAL_WIDTH_CONCENTRATION", + "Sensitivity": 8 + } + ], + "TextDetectionMode": { + "Mode": "TTDM_LINE", + "CharHeightRange": [ 5, 1000, 1 ], + "Direction": "HORIZONTAL", + "Sensitivity": 7 + } + } + ], + "TargetROIDefOptions" : + [ + { + "Name" : "ROI_OriginalImage", + "TaskSettingNameArray" : + [ + "Task_RecognizeMRZonPassport" + ] + } + ], + "CharacterModelOptions": [ + { + "Name" : "MRZ" + } + ], + "SemanticProcessingOptions": [ + { + "Name": "SP_Passport", + "ReferenceObjectFilter": { + "ReferenceTargetROIDefNameArray": ["ROI_OriginalImage"], + "AtomicResultTypeArray" : ["ART_TEXT_LINE"] + }, + "TaskSettingNameArray": [ + "ParsePassport" + ] + } + ], + "CodeParserTaskSettingOptions": [ + { + "Name": "ParsePassport", + "CodeSpecifications": ["MRTD_TD3_PASSPORT"] + } + ], + "GlobalParameter" : + { + "MaxTotalImageDimension" : 0 + } +} \ No newline at end of file