id-scanner-lib
Version:
Browser-based ID card, QR code, and face recognition scanner with liveness detection
474 lines (413 loc) • 13.4 kB
text/typescript
/**
* @file 身份证检测器
* @description 提供身份证检测和解析功能
* @module modules/id-card/id-card-detector
*/
import { EventEmitter } from '../../core/event-emitter';
import { Logger } from '../../core/logger';
import { Result } from '../../core/result';
import { IDCardType, IDCardEdge, IDCardInfo } from './types';
/**
* 身份证检测器配置选项
*/
export interface IDCardDetectorOptions {
/** 是否启用 */
enabled?: boolean;
/** 最小置信度 */
minConfidence?: number;
/** 是否检测身份证类型 */
detectType?: boolean;
/** 是否检测边缘 */
detectEdge?: boolean;
/** 是否启用边缘检测(用于更精确的边缘检测) */
enableEdgeDetection?: boolean;
/** 是否启用OCR识别 */
enableOCR?: boolean;
/** 是否裁剪并校正图像 */
cropAndAlign?: boolean;
/** 是否启用防伪检测 */
enableAntiFake?: boolean;
/** 是否返回原始图像 */
returnImage?: boolean;
/** 模型路径 */
modelPath?: string;
}
/**
* 图像处理配置选项
*/
export interface ImageProcessOptions {
/** 是否进行预处理 */
preprocess?: boolean;
/** 是否校正图像 */
correctPerspective?: boolean;
/** 是否增强图像 */
enhance?: boolean;
/** 是否去噪 */
denoise?: boolean;
/** 是否二值化 */
binarize?: boolean;
}
/**
* 身份证检测器类
*/
export class IDCardDetector extends EventEmitter {
private options: IDCardDetectorOptions;
private logger: Logger;
private initialized: boolean = false;
private models: {
detection?: any;
ocr?: any;
antiFake?: any;
} = {};
/**
* 构造函数
* @param options 配置选项
*/
constructor(options: IDCardDetectorOptions = {}) {
super();
this.options = {
enabled: true,
minConfidence: 0.7,
detectType: true,
detectEdge: true,
enableEdgeDetection: false,
enableOCR: true,
cropAndAlign: true,
enableAntiFake: false,
returnImage: false,
modelPath: '/models/id-card',
...options
};
this.logger = Logger.getInstance();
}
/**
* 初始化检测器
*/
public async initialize(): Promise<void> {
if (this.initialized || !this.options.enabled) {
return;
}
this.logger.debug('IDCardDetector', '初始化身份证检测器');
try {
// 加载检测模型
await this.loadDetectionModel();
// 如果启用OCR,加载OCR模型
if (this.options.enableOCR) {
await this.loadOCRModel();
}
// 如果启用防伪检测,加载防伪模型
if (this.options.enableAntiFake) {
await this.loadAntiFakeModel();
}
this.initialized = true;
this.emit('detector:initialized', {});
this.logger.debug('IDCardDetector', '身份证检测器初始化完成');
} catch (error) {
this.logger.error('IDCardDetector', '身份证检测器初始化失败', error as Error);
throw error;
}
}
/**
* 加载检测模型
* @private
*/
private async loadDetectionModel(): Promise<void> {
// 实际项目中,这里应该加载检测模型
this.logger.debug('IDCardDetector', '加载身份证检测模型');
// 模拟加载模型的延迟
await new Promise(resolve => setTimeout(resolve, 100));
// 设置模型
this.models.detection = {
loaded: true,
name: 'id-card-detection'
};
}
/**
* 加载OCR模型
* @private
*/
private async loadOCRModel(): Promise<void> {
// 实际项目中,这里应该加载OCR模型
this.logger.debug('IDCardDetector', '加载身份证OCR模型');
// 模拟加载模型的延迟
await new Promise(resolve => setTimeout(resolve, 100));
// 设置模型
this.models.ocr = {
loaded: true,
name: 'id-card-ocr'
};
}
/**
* 加载防伪模型
* @private
*/
private async loadAntiFakeModel(): Promise<void> {
// 实际项目中,这里应该加载防伪模型
this.logger.debug('IDCardDetector', '加载身份证防伪模型');
// 模拟加载模型的延迟
await new Promise(resolve => setTimeout(resolve, 100));
// 设置模型
this.models.antiFake = {
loaded: true,
name: 'id-card-anti-fake'
};
}
/**
* 处理图像
* @param image 图像源(可以是ImageData、HTMLImageElement、HTMLCanvasElement等)
* @param processOptions 图像处理选项
* @returns 处理结果
*/
public async processImage(
image: ImageData | HTMLImageElement | HTMLCanvasElement,
processOptions: ImageProcessOptions = {}
): Promise<Result<IDCardInfo>> {
if (!this.initialized) {
return Result.failure(new Error('身份证检测器未初始化'));
}
try {
this.logger.debug('IDCardDetector', '开始处理图像');
// 预处理图像
const processedImage = await this.preprocessImage(image, processOptions);
// 检测身份证
const detectionResult = await this.detectIDCard(processedImage);
if (!detectionResult || detectionResult.confidence < (this.options.minConfidence || 0.7)) {
return Result.failure(new Error('未检测到身份证或置信度过低'));
}
let idCardInfo: IDCardInfo = {
type: detectionResult.type,
edge: detectionResult.edge,
confidence: detectionResult.confidence
};
// 如果启用OCR识别,提取文字信息
if (this.options.enableOCR && this.models.ocr) {
// 裁剪并校正图像
const alignedImage = this.options.cropAndAlign ?
await this.cropAndAlign(processedImage, detectionResult.edge) :
processedImage;
// 识别文字
const ocrResult = await this.recognizeText(alignedImage, detectionResult.type);
// 合并结果
idCardInfo = {
...idCardInfo,
...ocrResult
};
}
// 如果启用防伪检测,进行防伪检测
if (this.options.enableAntiFake && this.models.antiFake) {
const antiFakeResult = await this.detectAntiFake(processedImage, detectionResult);
idCardInfo.antiFake = antiFakeResult;
}
// 如果需要返回原始图像
if (this.options.returnImage) {
// 根据图像类型获取ImageData
if (image instanceof ImageData) {
idCardInfo.image = image;
} else if (image instanceof HTMLCanvasElement) {
const context = image.getContext('2d');
if (context) {
idCardInfo.image = context.getImageData(0, 0, image.width, image.height);
}
} else if (image instanceof HTMLImageElement && image.complete) {
const canvas = document.createElement('canvas');
canvas.width = image.naturalWidth;
canvas.height = image.naturalHeight;
const context = canvas.getContext('2d');
if (context) {
context.drawImage(image, 0, 0);
idCardInfo.image = context.getImageData(0, 0, canvas.width, canvas.height);
}
}
}
this.logger.debug('IDCardDetector', '图像处理完成');
this.emit('detector:result', { result: idCardInfo });
return Result.success(idCardInfo);
} catch (error) {
this.logger.error('IDCardDetector', '图像处理失败', error as Error);
return Result.failure(error as Error);
}
}
/**
* 预处理图像
* @param image 图像源
* @param options 处理选项
* @returns 处理后的图像
* @private
*/
private async preprocessImage(
image: ImageData | HTMLImageElement | HTMLCanvasElement,
options: ImageProcessOptions
): Promise<ImageData> {
// 实际项目中,这里应该对图像进行预处理
this.logger.debug('IDCardDetector', '预处理图像');
// 创建ImageData对象
let imageData: ImageData;
if (image instanceof ImageData) {
imageData = image;
} else {
const canvas = document.createElement('canvas');
const width = image instanceof HTMLImageElement ? image.naturalWidth : image.width;
const height = image instanceof HTMLImageElement ? image.naturalHeight : image.height;
canvas.width = width;
canvas.height = height;
const context = canvas.getContext('2d');
if (!context) {
throw new Error('无法获取Canvas上下文');
}
if (image instanceof HTMLImageElement) {
context.drawImage(image, 0, 0);
} else {
context.drawImage(image, 0, 0);
}
imageData = context.getImageData(0, 0, width, height);
}
// 应用图像处理选项
// 实际项目中,这里应该根据options进行相应的图像处理
return imageData;
}
/**
* 检测身份证
* @param image 图像数据
* @returns 检测结果
* @private
*/
private async detectIDCard(image: ImageData): Promise<{
type: IDCardType;
edge: IDCardEdge;
confidence: number;
} | null> {
// 实际项目中,这里应该调用模型进行身份证检测
this.logger.debug('IDCardDetector', '检测身份证');
// 模拟检测结果
// 在实际应用中,这里应该使用机器学习模型进行推理
return {
type: IDCardType.FRONT,
edge: {
topLeft: { x: 10, y: 10 },
topRight: { x: image.width - 10, y: 10 },
bottomRight: { x: image.width - 10, y: image.height - 10 },
bottomLeft: { x: 10, y: image.height - 10 }
},
confidence: 0.95
};
}
/**
* 裁剪并校正图像
* @param image 图像数据
* @param edge 边缘信息
* @returns 校正后的图像
* @private
*/
private async cropAndAlign(image: ImageData, edge: IDCardEdge): Promise<ImageData> {
// 实际项目中,这里应该进行透视变换以校正图像
this.logger.debug('IDCardDetector', '裁剪并校正图像');
// 创建Canvas
const canvas = document.createElement('canvas');
// 设置标准身份证尺寸比例
canvas.width = 428;
canvas.height = 270;
const context = canvas.getContext('2d');
if (!context) {
throw new Error('无法获取Canvas上下文');
}
// 创建临时Canvas
const tempCanvas = document.createElement('canvas');
tempCanvas.width = image.width;
tempCanvas.height = image.height;
const tempContext = tempCanvas.getContext('2d');
if (!tempContext) {
throw new Error('无法获取临时Canvas上下文');
}
// 将ImageData绘制到临时Canvas
tempContext.putImageData(image, 0, 0);
// 在实际应用中,这里应该使用透视变换算法
// 例如使用Canvas的transform或WebGL进行变换
// 简化处理:直接裁剪
context.drawImage(
tempCanvas,
edge.topLeft.x,
edge.topLeft.y,
edge.topRight.x - edge.topLeft.x,
edge.bottomLeft.y - edge.topLeft.y,
0,
0,
canvas.width,
canvas.height
);
return context.getImageData(0, 0, canvas.width, canvas.height);
}
/**
* 识别文字
* @param image 图像数据
* @param type 身份证类型
* @returns 识别结果
* @private
*/
private async recognizeText(image: ImageData, type: IDCardType): Promise<Partial<IDCardInfo>> {
// 实际项目中,这里应该调用OCR模型进行文字识别
this.logger.debug('IDCardDetector', '识别文字');
// 模拟OCR结果
// 在实际应用中,这里应该使用OCR模型进行文字识别
if (type === IDCardType.FRONT) {
return {
name: '张三',
gender: '男',
ethnicity: '汉',
birthDate: '1990-01-01',
address: '北京市朝阳区某某街道某某社区1号楼1单元101',
idNumber: '110101199001010001',
photoRegion: {
x: 300,
y: 40,
width: 100,
height: 130
}
};
} else if (type === IDCardType.BACK) {
return {
issueAuthority: '北京市公安局朝阳分局',
validFrom: '2015-01-01',
validTo: '2035-01-01'
};
}
return {};
}
/**
* 检测防伪特征
* @param image 图像数据
* @param detectionResult 检测结果
* @returns 防伪检测结果
* @private
*/
private async detectAntiFake(
image: ImageData,
detectionResult: { type: IDCardType; edge: IDCardEdge; confidence: number }
): Promise<IDCardInfo['antiFake']> {
// 实际项目中,这里应该调用防伪模型进行特征检测
this.logger.debug('IDCardDetector', '检测防伪特征');
// 模拟防伪检测结果
// 在实际应用中,这里应该使用机器学习模型检测防伪特征
return {
passed: true,
score: 0.92,
features: {
fluorescent: true,
microtext: true,
opticalVariable: true,
texture: true,
watermark: true
}
};
}
/**
* 释放资源
*/
public dispose(): void {
this.logger.debug('IDCardDetector', '释放资源');
// 清理模型
this.models = {};
this.initialized = false;
// 清理事件监听
this.removeAllListeners();
}
}