Dynamsoft Document Normalizer JavaScript SDK позволяет обнаруживать и исправлять документы. Его можно легко интегрировать в веб-приложения с помощью нескольких строк кода. В этой статье мы создадим веб-приложение Angular, чтобы продемонстрировать, как использовать SDK для обнаружения и исправления документов.

Предпосылки

Angular CLI: инструмент интерфейса командной строки для разработки на Angular.

npm install -g @angular/cli

Инициализировать проект Angular и установить зависимости

В терминале выполните следующую команду, чтобы создать новый проект Angular.

ng new angular-document-edge-detection

Затем перейдите в папку проекта и установите зависимости.

  • Dynamsoft Document Normalizer: JavaScript SDK для обнаружения и исправления документов.
npm i dynamsoft-document-normalizer
  • Dynamsoft Camera Enhancer: JavaScript SDK для управления камерой, захвата изображений и потоковой передачи видео.
npm i dynamsoft-camera-enhancer

Затем откройте файл angular.json, чтобы настроить путь к ресурсу для Dynamsoft Document Normalizer:

"assets": [
  "src/favicon.ico",
  "src/assets",
  {
    "glob": "**/*",
    "input": "./node_modules/dynamsoft-document-normalizer/dist",
    "output": "assets/dynamsoft-document-normalizer"
  }
],

Выходной путь должен быть таким же, как указанный в коде. Мы создаем файл dynamsoft.service.ts для установки лицензионного ключа и пути к ресурсу:

import { Injectable, Optional } from '@angular/core';
import { DocumentNormalizer} from 'dynamsoft-document-normalizer';

@Injectable({
  providedIn: 'root'
})
export class DynamsoftService {

  constructor() {
    DocumentNormalizer.license = "DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAxLTE2NDk4Mjk3OTI2MzUiLCJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSIsInNlc3Npb25QYXNzd29yZCI6IndTcGR6Vm05WDJrcEQ5YUoifQ==";
    DocumentNormalizer.engineResourcePath = "assets/dynamsoft-document-normalizer";
  }
}

Параметры алгоритма обнаружения и исправления документов

Dynamsoft Document Normalizer предоставляет набор параметров для управления процессом обнаружения и исправления. Вы можете обратиться к онлайн-документации для более подробной информации.

Здесь мы создаем несколько простых шаблонов параметров с многострочными строками TypeScript:

export class Template {
    static binary = `
      {
          "GlobalParameter":{
              "Name":"GP"
          },
          "ImageParameterArray":[
              {
                  "Name":"IP-1",
                  "NormalizerParameterName":"NP-1",
                  "BinarizationModes":[{"Mode":"BM_LOCAL_BLOCK", "ThresholdCompensation":9}],
                  "ScaleDownThreshold":2300
              }
          ],
          "NormalizerParameterArray":[
              {
                  "Name":"NP-1",
                  "ColourMode": "ICM_BINARY" 
              }
          ]
      }
      `;

    static color = `
      {
          "GlobalParameter":{
              "Name":"GP"
          },
          "ImageParameterArray":[
              {
                  "Name":"IP-1",
                  "NormalizerParameterName":"NP-1",
                  "BinarizationModes":[{"Mode":"BM_LOCAL_BLOCK", "ThresholdCompensation":9}],
                  "ScaleDownThreshold":2300
              }
          ],
          "NormalizerParameterArray":[
              {
                  "Name":"NP-1",
                  "ColourMode": "ICM_COLOUR" 
              }
          ]
      }
      `;

    static grayscale = `
      {
          "GlobalParameter":{
              "Name":"GP"
          },
          "ImageParameterArray":[
              {
                  "Name":"IP-1",
                  "NormalizerParameterName":"NP-1",
                  "BinarizationModes":[{"Mode":"BM_LOCAL_BLOCK", "ThresholdCompensation":9}],
                  "ScaleDownThreshold":2300
              }
          ],
          "NormalizerParameterArray":[
              {
                  "Name":"NP-1",
                  "ColourMode": "ICM_GRAYSCALE"
              }
          ]
      }
      `;
}

Единственная разница между этими тремя шаблонами — это параметр ColourMode. Параметр ColourMode управляет цветовым режимом выходного изображения. Он может быть установлен на ICM_BINARY, ICM_COLOUR или ICM_GRAYSCALE.

Компоненты Angular для обнаружения и исправления документов

Мы собираемся создать два компонента Angular: file и camera. Компонент file отвечает за обнаружение и исправление документов из файлов изображений. Компонент camera отвечает за обнаружение и исправление документов из потока камеры.

ng generate component file-detection
ng generate component camera-detection

Компонент обнаружения файлов

Страница обнаружения файлов состоит из пяти частей: группы переключателей, кнопки ввода файла, элемента изображения, холста и элемента div.

<span id="loading-status" style="font-size:x-large" [hidden]="isLoaded">Loading Library...</span>
<br />

<div class="row">

    <label for="binary"> <input type="radio" name="templates" value="binary" (change)="onRadioChange($event)" />Black &
        White </label>


    <label for="grayscale"><input type="radio" name="templates" value="grayscale" (change)="onRadioChange($event)" />
        Grayscale </label>

    <label for="color"><input type="radio" name="templates" value="color" [checked]="true"
            (change)="onRadioChange($event)" /> Color </label>
</div>

<input type="file" title="file" id="file" accept="image/*" (change)="onChange($event)" />

<div class="container">
    <div id="imageview">
        <img id="image" alt="" />
        <canvas id="overlay"></canvas>
    </div>

    <div id="resultview">
        <canvas id="normalizedImage"></canvas>
    </div>

</div>

В TypeScript мы сначала внедряем DynamsoftService в конструктор и инициализируем объект Dynamsoft Document Normalizer в методе ngOnInit.

import { Component, OnInit } from '@angular/core';
import { DocumentNormalizer } from 'dynamsoft-document-normalizer';
import { DynamsoftService } from '../dynamsoft.service';
import { OverlayManager } from '../overlay';
import { Template } from '../template';

@Component({
  selector: 'app-file-detection',
  templateUrl: './file-detection.component.html',
  styleUrls: ['./file-detection.component.css']
})
export class FileDetectionComponent implements OnInit {
  isLoaded = false;
  overlay: HTMLCanvasElement | undefined;
  context: CanvasRenderingContext2D | undefined;
  normalizer: DocumentNormalizer | undefined;
  overlayManager: OverlayManager;
  points: any[] = [];
  currentFile: File | undefined;

  constructor(private dynamsoftService: DynamsoftService) {
    this.overlayManager = new OverlayManager();
  }

  ngOnInit(): void {
    this.overlayManager.initOverlay(document.getElementById('overlay') as HTMLCanvasElement);
    (async () => {
      this.normalizer = await DocumentNormalizer.createInstance();
      this.isLoaded = true;
      await this.normalizer.setRuntimeSettings(Template.color);
    })();
  }
}

Затем добавьте реализации для элементов пользовательского интерфейса.

  • Группа переключателей используется для переключения шаблона параметра.
onRadioChange(event: Event) {
    if (!this.normalizer) {
      return;
    }

    let target = event.target as HTMLInputElement;
    let template = Template.binary;
    if (target.value === 'binary') {
      template = Template.binary;
    } else if (target.value === 'grayscale') {
      template = Template.grayscale;
    } else if (target.value === 'color') {
      template = Template.color;
    }
    (async () => {
      await this.normalizer!.setRuntimeSettings(template);
      this.normalize(this.currentFile!, this. Points);
    })();
  }
  • Кнопка ввода файла используется для выбора файла изображения.
onChange(event: Event) {
    const element = event.currentTarget as HTMLInputElement;
    let fileList: FileList | null = element.files;
    if (fileList) {
      let file = fileList.item(0) as any;
        
    }
  }
  • Элемент изображения используется для отображения загруженного файла изображения.
if (file) {
    this.currentFile = file;
    let fr = new FileReader();
    fr.onload = (event: any) => {
      let image = document.getElementById('image') as HTMLImageElement;
      if (image) {
        image.src = event.target.result;
        const img = new Image();

        img.onload = (event: any) => {
            
        };
        img.src = event.target.result;
      }
    };
    fr.readAsDataURL(file);

Когда изображение загружено, вызовите метод detectQuad для обнаружения краев документа.

this.normalizer.detectQuad(file).then((results: any) => {
    try {
      if (results.length > 0) {
          
      } 
    } catch (e) {
      alert(e);
    }
  });
  • Холст используется для отображения обнаруженных краев документа.
try {
    if (results.length > 0) {
      let result = results[0];
        this.points = result['location']['points'];
        this.overlayManager.drawOverlay(
          this.points,
        );
    } 
  } catch (e) {
    alert(e);
  }
  • Элемент div используется для отображения исправленного документа.
this.normalize(file, this.points);

  normalize(file: File, points: any) {
    if (this.normalizer) {
      this.normalizer.normalize(file, points).then((result: any) => {
        let image = document.getElementById('normalizedImage') as HTMLCanvasElement;
        if (image) {
          image.width = result.image.width;
          image.height = result.image.height;
          let ctx = image.getContext('2d') as CanvasRenderingContext2D;

          var imgdata = ctx.createImageData(image.width, image.height);
          var imgdatalen = result.image.data.length;
          for(var i=0; i<imgdatalen; i++) {
              imgdata.data[i] = result.image.data[i];
          }
          ctx.putImageData(imgdata, 0, 0);
        }
      });
    }
  }

Компонент обнаружения камеры

Пользовательский интерфейс компонента обнаружения камеры аналогичен компоненту обнаружения файлов. Разница в том, что элемент изображения заменяется элементом видео.

<div id="document-scanner">
    <span id="loading-status" style="font-size:x-large" [hidden]="isLoaded">Loading Library...</span>
    <br />
    <div class="row">

        <label for="binary"> <input type="radio" name="templates" value="binary" (change)="onRadioChange($event)" />Black &
            White </label>
    
    
        <label for="grayscale"><input type="radio" name="templates" value="grayscale" (change)="onRadioChange($event)" />
            Grayscale </label>
    
        <label for="color"><input type="radio" name="templates" value="color" [checked]="true"
                (change)="onRadioChange($event)" /> Color </label>
    </div>
    <label for="threshold"><input id="thredshold" (change)="updateThresholdCompensation($event)" type="range" min="0" max="10" value="9" step="1">Edge Detection Threshold: <span id="ThresholdCompensationval" style="width: 12px;" [textContent]="9"></span></label>
    <div>
        <label for="videoSource">Video Source: 
        <select id="videoSource" (change)="openCamera()"></select></label>
        <button id="detectButton" (click)="detectDocument()">Start Detection</button>
        <button id="captureButton" (click)="captureDocument()">Capture Document</button>
    </div>

    <div id="videoview">
        <div class="dce-video-container" id="videoContainer"></div>
        <canvas id="overlay"></canvas>
    </div>

    <div class="container">
        <div id="resultview">
            <canvas id="normalizedImage"></canvas>
        </div>
    </div>
</div>

Известно, что метод getUserMedia — единственный API, который может получить доступ к потоку камеры. С методом getUserMedia у нас еще много работы по программированию камеры. Вместо этого для упрощения кодирования мы используем Dynamsoft Camera Enhancer. SDK камеры JavaScript инкапсулирует метод getUserMedia и предоставляет несколько полезных API.

Вот код инициализации компонента обнаружения камеры.

import { Component, OnInit } from '@angular/core';
import { DocumentNormalizer } from 'dynamsoft-document-normalizer';
import { CameraEnhancer } from 'dynamsoft-camera-enhancer';
import { DynamsoftService } from '../dynamsoft.service';
import { OverlayManager } from '../overlay';
import { Template } from '../template';

@Component({
  selector: 'app-camera-detection',
  templateUrl: './camera-detection.component.html',
  styleUrls: ['./camera-detection.component.css']
})
export class CameraDetectionComponent implements OnInit {
  isLoaded = false;
  overlay: HTMLCanvasElement | undefined;
  context: CanvasRenderingContext2D | undefined;
  normalizer: DocumentNormalizer | undefined;
  overlayManager: OverlayManager;
  currentData: any;
  cameraInfo: any = {};
  videoSelect: HTMLSelectElement | undefined;
  enhancer: CameraEnhancer | undefined;
  isDetecting = false;
  captured: any[] = [];

  constructor(private dynamsoftService: DynamsoftService) {
    this.overlayManager = new OverlayManager();
  }

  ngOnDestroy() {
    this.normalizer?.dispose();
    this.normalizer = undefined;

    this.enhancer?.dispose(true);
    this.enhancer = undefined;
  }

  ngOnInit(): void {
    this.videoSelect = document.querySelector('select#videoSource') as HTMLSelectElement;
    this.overlayManager.initOverlay(document.getElementById('overlay') as HTMLCanvasElement);
    (async () => {
      this.normalizer = await DocumentNormalizer.createInstance();
      this.enhancer = await CameraEnhancer.createInstance();
      this.enhancer.on("cameraOpen", (playCallBackInfo: any) => {
        this.overlayManager.updateOverlay(playCallBackInfo.width, playCallBackInfo.height);
      });
      this.enhancer.on("cameraClose", (playCallBackInfo: any) => {
        console.log(playCallBackInfo.deviceId);
      });


      this.isLoaded = true;
      await this.normalizer.setRuntimeSettings(Template.color);
    })();
  }
}

После создания экземпляра Dynamsoft Camera Enhancer нам нужно привязать его к элементу div для отображения потока камеры.

let uiElement = document.getElementById('videoContainer');
if (uiElement) {
  await this.enhancer.setUIElement(uiElement);
}

Если у вас несколько камер, вы можете вызвать метод getAllCameras, чтобы получить список камер:

let cameras = await this.enhancer.getAllCameras();
this.listCameras(cameras);

Затем выберите камеру и откройте ее:

async openCamera(): Promise<void> {
  if (this.videoSelect) {
    let deviceId = this.videoSelect.value;
    if (this.enhancer) {
      await this.enhancer.selectCamera(this.cameraInfo[deviceId]);
      await this.enhancer.open()
    }
  }

}

Метод detectQuad поддерживает различные типы ввода. В приведенном выше разделе мы использовали метод detectQuad для обнаружения документа из файла изображения. Теперь мы используем его для обнаружения документа из кадров потока камеры.

detect(): void {
  if (this.normalizer && this.enhancer && this.isDetecting) {

    let data = this.enhancer.getFrame().toCanvas();
    this.normalizer.detectQuad(data).then((results: any) => {
      this.overlayManager.clearOverlay();
      if (!this.isDetecting) return;
      try {
        if (results.length > 0) {
          if (this.captured.length > 0) {
            this.captured.pop();
          }

          let result = results[0];
          let points = result['location']['points'];
          this.captured.push({ 'image': data, 'points': points });
          this.overlayManager.drawOverlay(
            points,
          );
        }
      } catch (e) {
        alert(e);
      }
      this. Detect();
    });
  }
}

Задание обнаружения документов выполняется в веб-воркере. Таким образом, не вызывайте его постоянно. Вы должны дождаться завершения предыдущего задания обнаружения, прежде чем вызывать его снова.

Запустите ng serve, чтобы повеселиться с приложением для обнаружения и проверки веб-документов.

Исходный код

https://github.com/yushulx/angular-document-edge-detection

Первоначально опубликовано на https://www.dynamsoft.com 5 января 2023 г.