Dynamsoft Document Normalizer JavaScript Edition User Guide
With Dynamsoft Document Normalizer JavaScript edition, you can add to your website the ability to take pictures of documents with your camera and normalize them to obtain high-quality images for further processing or archiving purposes.
Dynamsoft Document Normalizer v2.0.11 and above is based on Dynamsoft Capture Vision Architecture. To learn more, read Introduction to Dynamsoft Capture Vision.
In this guide, you’ll learn step-by-step how to build such a simple solution in a web page.
Table of Contents
- Dynamsoft Document Normalizer JavaScript Edition User Guide
Getting started
The solution consists of two steps
- Detect the document boundaries
- Normalize the document based on the detected boundaries
The following sample code sets up the SDK and implements boundary detection on a web page, which is just the first step in capturing a normalized image of your document. We’ll cover the second step later in Building Your Own Page.
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="https://cdn.jsdelivr.net/npm/dynamsoft-core@3.0.10/dist/core.js"></script>
<script src="https://cdn.jsdelivr.net/npm/dynamsoft-utility@1.0.10/dist/utility.js"></script>
<script src="https://cdn.jsdelivr.net/npm/dynamsoft-document-normalizer@2.0.11/dist/ddn.js"></script>
<script src="https://cdn.jsdelivr.net/npm/dynamsoft-capture-vision-router-for-ddn2.0.11@2.0.11/dist/cvr.js"></script>
<script src="https://cdn.jsdelivr.net/npm/dynamsoft-camera-enhancer@4.0.0/dist/dce.js"></script>
</head>
<body>
<h1>Detect the Boundary of the Document</h1>
<button onclick="startDetection()">Start Detection</button>
<div id="cameraViewContainer" style="width: 50vw; height: 45vh; margin-top: 10px; display: none"></div>
<script>
const cameraViewContainer = document.querySelector(
"#cameraViewContainer"
);
let router;
let cameraEnhancer;
Dynamsoft.License.LicenseManager.initLicense(
"DLS2eyJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSJ9"
);
Dynamsoft.CVR.CaptureVisionRouter.preloadModule(["DDN"]);
(async function() {
router = await Dynamsoft.CVR.CaptureVisionRouter.createInstance();
let view = await Dynamsoft.DCE.CameraView.createInstance();
cameraEnhancer = await Dynamsoft.DCE.CameraEnhancer.createInstance(
view
);
cameraViewContainer.append(view.getUIElement());
router.setInput(cameraEnhancer);
})();
async function startDetection() {
cameraViewContainer.style.display = "block";
await cameraEnhancer.open();
await router.startCapturing("detect-document-boundaries");
};
</script>
</body>
</html>
About the code
-
Dynamsoft.CVR.LicenseManager.initLicense()
: initializes the license using a license key string. -
Dynamsoft.CVR.CaptureVisionRouter.preloadModule(["DDN"])
: preloads theDocumentNormalizer
module, saving time in preparing for document border detection and image normalization. -
Dynamsoft.CVR.CaptureVisionRouter.createInstance()
: initializes therouter
variable by creating an instance of theCaptureVisionRouter
class. An instance ofCaptureVisionRouter
is the core of any solution based on Dynamsoft Capture Vision architecture.Read more on what is CaptureVisionRouter
-
Dynamsoft.DCE.CameraEnhancer.createInstance(view)
: initializes thecameraEnhancer
variable by creating an instance of theCameraEnhancer
class. -
setInput()
: setscameraEnhancer
as the image source torouter
.In some cases, a different camera might be required instead of the default one. Also, a different resolution might work better. To change the camera or the resolution, use the
CameraEnhancer
instancecameraEnhancer
. Learn more here. -
startCapturing("detect-document-boundaries")
: starts to run images through a pre-defined process which, in the case of “detect-document-boundaries”, tries to find the boundary of a document present in the image(s).
Test the code
Create a text file called “Detect-A-Document-Boundary.html”, fill it with the code above and save it. After that, open the example page in your browser, allow the page to access your camera, and the video will be displayed on the page. Afterwards, you will see the detected boundaries displayed on the video in real time.
NOTE:
The sample code requires the following to run:
- Internet connection
- A supported browser
- An accessible Camera
Please note:
- Although the page should work properly when opened directly as a file (“file:///”), it’s recommended that you deploy it to a web server and access it via HTTPS.
- On first use, you need to wait a few seconds for the SDK to initialize.
- The license “DLS2eyJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSJ9” used in this sample is an online license good for 24 hours and requires network connection to work. To test the SDK further, you can request a 30-day trial license via the Request a Trial License link.
If the test doesn’t go as expected, you can contact us.
Building your own page
In this section, we’ll break down and show all the steps needed to build the solution in a web page.
We’ll build on this skeleton page:
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head>
<body>
<h1>Detect the Boundary of the Document and Normalize it</h1>
<script>
// Write your code here.
</script>
</body>
</html>
Include the SDK
To build the solution, we need to include five packages
dynamsoft-core
: Required, it includes basic classes, interfaces, and enumerations that are shared between all Dynamsoft SDKs.dynamsoft-utility
: Optional, it defines auxiliary classes shared between all Dynamsoft SDKs.dynamsoft-document-normalizer
: Required, it provides functions to detect boundaries or perform normalization.dynamsoft-capture-vision-router
: Required, it defines the classCaptureVisionRouter
, which controls the whole process.dynamsoft-camera-enhancer
: Recommended, it provides the ability to capture images from video stream.
Use a CDN
The simplest way to include the SDK is to use either the jsDelivr or UNPKG CDN.
-
jsDelivr
<script src="https://cdn.jsdelivr.net/npm/dynamsoft-core@3.0.10/dist/core.js"></script> <script src="https://cdn.jsdelivr.net/npm/dynamsoft-utility@1.0.10/dist/utility.js"></script> <script src="https://cdn.jsdelivr.net/npm/dynamsoft-document-normalizer@2.0.11/dist/ddn.js"></script> <script src="https://cdn.jsdelivr.net/npm/dynamsoft-capture-vision-router-for-ddn2.0.11@2.0.11/dist/cvr.js"></script> <script src="https://cdn.jsdelivr.net/npm/dynamsoft-camera-enhancer@4.0.0/dist/dce.js"></script>
-
UNPKG
<script src="https://unpkg.com/dynamsoft-core@3.0.10/dist/core.js"></script> <script src="https://unpkg.com/dynamsoft-utility@1.0.10/dist/utility.js"></script> <script src="https://unpkg.com/dynamsoft-document-normalizer@2.0.11/dist/ddn.js"></script> <script src="https://unpkg.com/dynamsoft-capture-vision-router-for-ddn2.0.11@2.0.11/dist/cvr.js"></script> <script src="https://unpkg.com/dynamsoft-camera-enhancer@4.0.0/dist/dce.js"></script>
Host the SDK yourself
Besides using the CDN, you can also download the SDK and host its files on your own website / server before including it in your application.
Options to download the SDK:
-
From the website
-
yarn
yarn add dynamsoft-capture-vision-router@2.0.11
-
npm
npm install dynamsoft-capture-vision-router@2.0.11
Note: Upon installation of
dynamsoft-capture-vision-router
, the compatible versions ofdynamsoft-document-normalizer
anddynamsoft-core
will be automatically downloaded.
Depending on how you downloaded the SDK and where you put it, you can typically include it like this:
<!-- Upon extracting the zip package into your project, you can generally include it in the following manner -->
<script src="./dynamsoft-document-normalizer-js-2.0.11/dynamsoft/distributables/dynamsoft-core@3.0.10/dist/core.js"></script>
<script src="./dynamsoft-document-normalizer-js-2.0.11/dynamsoft/distributables/dynamsoft-utility@1.0.10/dist/utility.js"></script>
<script src="./dynamsoft-document-normalizer-js-2.0.11/dynamsoft/distributables/dynamsoft-document-normalizer@2.0.11/dist/ddn.js"></script>
<script src="./dynamsoft-document-normalizer-js-2.0.11/dynamsoft/distributables/dynamsoft-capture-vision-router@2.0.11/dist/cvr.js"></script>
<script src="./dynamsoft-document-normalizer-js-2.0.11/dynamsoft/distributables/dynamsoft-camera-enhancer@4.0.0/dist/dce.js"></script>
or
<script src="./node_modules/dynamsoft-core/dist/core.js"></script>
<script src="./node_modules/dynamsoft-utility/dist/utility.js"></script>
<script src="./node_modules/dynamsoft-document-normalizer/dist/ddn.js"></script>
<script src="./node_modules/dynamsoft-capture-vision-router/dist/cvr.js"></script>
<script src="./node_modules/dynamsoft-camera-enhancer/dist/dce.js"></script>
Define necessary HTML elements
For this solution, we define two buttons and three <div>
elements.
<button onclick="startDetection()">Start Detection</button>
<button id="btn_normalize" onclick="normalizeImage()" disabled>Normalize Image</button>
<div id="cameraViewContainer" style="width: 50vw; height: 45vh; margin-top: 10px; display: none"></div>
<div id="imageEditorViewContainer" style="width: 80vw; height: 50vh"></div>
<div id="normalizedImageContainer"></div>
const cameraViewContainer = document.querySelector(
"#cameraViewContainer"
);
const imageEditorViewContainer = document.querySelector(
"#imageEditorViewContainer"
);
const normalizedImageContainer = document.querySelector(
"#normalizedImageContainer"
);
Prepare the SDK for the task
The following function executes as soon as the page loads to get the SDK prepared:
let router;
let cameraEnhancer;
let imageEditorView;
Dynamsoft.License.LicenseManager.initLicense(
"DLS2eyJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSJ9"
);
Dynamsoft.CVR.CaptureVisionRouter.preloadModule(["DDN"]);
(async function() {
router = await Dynamsoft.CVR.CaptureVisionRouter.createInstance();
let view = await Dynamsoft.DCE.CameraView.createInstance();
cameraEnhancer = await Dynamsoft.DCE.CameraEnhancer.createInstance(
view
);
cameraViewContainer.append(view.getUIElement());
router.setInput(cameraEnhancer);
if (resultReceiver)
router.addResultReceiver(resultReceiver);
})();
The code was explained earlier. Please refer to About the Code.
Start the detection
Before we start the detection process with startDetection()
, we need to define a callback function to accept the detected document boundaries. The callback function is defined based on the CapturedResultReceiver
interface.
Read more on
CapturedResultReceiver
const resultReceiver = new Dynamsoft.CVR.CapturedResultReceiver();
resultReceiver.onDetectedQuadsReceived = (result) => {
items = result.quadsResultItems;
console.log(items);
};
And we define the function like this:
async function startDetection() {
// Shows the cameraView
cameraViewContainer.style.display = "block";
// Specifies the result receiver
// Starts streaming the video
await cameraEnhancer.open();
// Uses the built-in template "detect-document-boundaries" to start a boundary detecting task
await router.startCapturing("detect-document-boundaries");
};
The steps of the workflow is as follows
cameraEnhancer
streams the video, captures live video frames and stores them in a buffer.router
gets the video frames fromcameraEnhancer
and passes them to be processed by an internalDocumentNormalizer
instance.- The internal
DocumentNormalizer
instance returns the found document boundaries, known asquadsResultItems
, torouter
. router
outputs thequadsResultItems
via the callback functiononDetectedQuadsReceived
.
Also note that the
quadsResultItems
are drawn over the video automatically to show the detection in action.
Review and adjust the boundary
The SDK tries to find the boundary of the document in each and every image processed. This happens very fast and we don’t always get the perfect boundary for normalization. Therefore, we can draw the image and the boundary in the ImageEditorView
and let the user adjust the boundary before proceed to normalization.
To do this, we define the function editBoundary()
like this
async function editBoundary(imageData, points) {
// Stops the previous detecting task since we assume we have found a good boundary.
router.stopCapturing();
// Creates an ImageEditorView inside the div "imageEditorViewContainer".
if (imageEditorView == undefined) {
imageEditorView = await Dynamsoft.DCE.ImageEditorView.createInstance(
imageEditorViewContainer
);
}
// Creates a drawinglayer to draw the document boundary (quad).
let layer;
if (imageEditorView.getAllDrawingLayers().length > 0)
layer = imageEditorView.getAllDrawingLayers()[0];
else layer = imageEditorView.createDrawingLayer();
// Hides the cameraView and show the imageEditorView.
cameraViewContainer.style.display = "none";
imageEditorViewContainer.style.display = "block";
// Draws the image in the imageEditorView first.
imageEditorView.setOriginalImage(imageData);
// Draws the document boundary (quad).
const quad = new Dynamsoft.DCE.DrawingItem.QuadDrawingItem({
points,
});
layer.setDrawingItems([quad]);
document.getElementById("btn_normalize").disabled = false;
};
Since we will need the original image returned, we update startDetection()
like this:
async function startDetection() {
cameraViewContainer.style.display = "block";
await cameraEnhancer.open();
// Updates the settings for "detect-document-boundaries" to return the original image.
let settings = await router.getSimplifiedSettings(
"detect-document-boundaries"
);
settings.capturedResultItemTypes |=
Dynamsoft.Core.EnumCapturedResultItemType.CRIT_ORIGINAL_IMAGE;
await router.updateSettings("detect-document-boundaries", settings);
await router.startCapturing("detect-document-boundaries");
};
Then we update the callback function to do 2 things:
- Determine whether a found boundary is good by counting consecutive frames with results.
To judge whether the boundary is good enough, here we set 30 consecutive fames with results as the threshold. Alternatively, the quality of the boundary can be judged in a few other different ways. For example:
- With its property
confidenceAsDocumentBoundary
.if (result.items[1] && result.items[1].confidenceAsDocumentBoundary > 80) { editBoundary(image, result.items[1].location.points); }
- With a result filter such as a
MultiFrameResultCrossFilter
. The following code should be executed before startCapturing() is called.filter = new Dynamsoft.Utility.MultiFrameResultCrossFilter(); filter.enableResultCrossVerification(Dynamsoft.Core.EnumCapturedResultItemType.CRIT_DETECTED_QUAD, true); await router.addResultFilter(filter);
- With its property
- If a good boundary is found, carry on to invoke the function
editBoundary()
.Note that in order to get both the boundary result and the original image, we have changed the callback function from
onDetectedQuadsReceived
toonCapturedResultReceived
.
let frameCount = 0;
resultReceiver.onCapturedResultReceived = (result) => {
/* Saves the image data of the current frame for subsequent image editing. */
let image = result.items.filter((item) => item.type === 1)[0].imageData;
if (result.items.length <= 1) {
frameCount = 0;
return;
}
frameCount++;
if (frameCount === 30) {
frameCount = 0;
editBoundary(image, result.items[1].location.points);
}
};
Now, the behavior will be
- The page constantly detect the boundary of the document in the video.
- When the found boundary in 30 consecutive image frames, the page hides the video stream and draw both the image and the boundary in the “imageEditorViewer”.
- The user can adjust the boundary to be more precise.
Normalize the document
After the user has adjusted the boundary or determined that the found boundary is good enough, he can press the button “Normalize Image” to carry out the normalization as the last step of the solution.
The function normalizeImage()
is defined like this:
async function normalizeImage() {
// Hides the imageEditorView
imageEditorViewContainer.style.display = "none";
// Removes the old normalized image if any
normalizedImageContainer.innerHTML = "";
// Gets the boundary from the imageEditorView
let boundaryQuad = null;
if (imageEditorView == undefined) {
document.getElementById("btn_normalize").disabled = true;
return;
}
let seletedItems = imageEditorView.getSelectedDrawingItems();
if (seletedItems.length == 0) {
let layer = imageEditorView.getAllDrawingLayers()[0];
boundaryQuad = layer.getDrawingItems()[0];
} else {
boundaryQuad = seletedItems[0];
}
// Gets the original image to normalize
let originalImage = imageEditorView.getOriginalImage();
await router.resetSettings();
let quad = boundaryQuad.getQuad();
// Updates the template "normalize-document" with the boundary
let newSettings = await router.getSimplifiedSettings(
"normalize-document"
);
newSettings.roi.points = quad.points;
newSettings.roiMeasuredInPercentage = 0;
await router.updateSettings("normalize-document", newSettings);
// Normalizes and show the result image
let normalizeResult = await router.capture(
originalImage,
"normalize-document"
);
normalizedImageContainer.append(normalizeResult.items[0].toCanvas());
document.getElementById("btn_normalize").disabled = true;
};
The added behavior is
- The user hits “Normalize Image”
- The page gets the boundary normalize the image based on it
- The normalized image shows up on the page
Output the document as a file
We can output the document as a file with the help of the class Dynamsoft.Utility.ImageManager
. To do this, we change the following line in the function “normalizeImage()”:
normalizedImageContainer.append(normalizeResult.items[0].toCanvas());
to
const imageManager = new Dynamsoft.Utility.ImageManager();
imageManager.saveToFile(normalizeResult.items[0].imageData, "result.jpg", true);
Then once a document has been normalized, it is downloaded as JPEG file in the browser.
System requirements
The SDK requires the following features to work:
-
Secure context (HTTPS deployment)
When deploying your application / website for production, make sure to serve it via a secure HTTPS connection. This is required for two reasons
- Access to the camera video stream is only granted in a security context. Most browsers impose this restriction.
Some browsers like Chrome may grant the access for
http://127.0.0.1
andhttp://localhost
or even for pages opened directly from the local disk (file:///...
). This can be helpful for temporary development and test. - Dynamsoft License requires a secure context to work.
- Access to the camera video stream is only granted in a security context. Most browsers impose this restriction.
-
WebAssembly
,Blob
,URL
/createObjectURL
,Web Workers
The above four features are required for the SDK to work.
The following table is a list of supported browsers based on the above requirements:
Browser Name | Version |
---|---|
Chrome | v69+ |
Firefox | v62+ |
Safari | v15+ |
Edge | v79+ |
Apart from the browsers, the operating systems may impose some limitations of their own that could restrict the use of the SDK.
Release notes
Learn what are included in each release at https://www.dynamsoft.com/document-normalizer/docs/programming/javascript/release-notes/?ver=latest.
Next steps
Now that you have got the SDK integrated, you can choose to move forward in the following directions
- Check out the official samples.
- Learn about the available APIs.