zp_cccd_scanner 1.4.0
zp_cccd_scanner: ^1.4.0 copied to clipboard
Flutter plugin quét và nhận diện thẻ CCCD / Căn cước Việt Nam (đủ 3 mẫu thẻ, 2 mặt) bằng camera native trên iOS (AVFoundation + Vision) và Android (CameraX + ML Kit).
zp_cccd_scanner #
Flutter plugin quét và nhận diện thẻ CCCD / Căn cước Việt Nam bằng camera native, hiệu ứng giống Zalo: khi phát hiện đúng thẻ trong khung, frame đóng băng và tia quét chạy từ trên xuống.
- iOS: AVFoundation + Vision (nhận diện chữ, QR, PDF417)
- Android: CameraX + ML Kit bundled (Text Recognition Latin, Barcode)
- Toàn bộ xử lý trên máy, không gửi ảnh ra ngoài
Thẻ được hỗ trợ #
| Mẫu thẻ | Mặt trước | Mặt sau |
|---|---|---|
| CCCD mã vạch (2016–2021) | ✅ | ✅ (PDF417 / cơ quan cấp, cả mẫu trước và sau 2019) |
| CCCD gắn chip (2021–06/2024) | ✅ (QR) | ✅ (MRZ) |
| Thẻ Căn cước (từ 07/2024) | ✅ | ✅ (QR + MRZ) |
CMND (hết hiệu lực từ 01/01/2025), GPLX, hộ chiếu, thẻ ngân hàng, giấy chứng nhận căn cước... đều bị loại.
Chống nhận nhầm #
Chỉ chấp nhận khi có bằng chứng mạnh, rồi xác nhận lại lần 2 trên chính frame đã đóng băng:
- MRZ (mặt sau thẻ chip / Căn cước):
IDVNM, kiểm tra đủ 5 check digit ICAO 9303 (gồm check digit riêng của số định danh 12 số). - PDF417 trải ngang mép trên + cụm chữ đặc trưng (mặt sau CCCD mã vạch).
- Mặt trước: so khớp cả dòng tiêu đề, số định danh hợp lệ (mã tỉnh, thế kỷ, năm sinh) và khớp chéo với ngày sinh / giới tính in trên thẻ.
- Thẻ phải nằm trọn trong khung: dò 4 cạnh thẻ trên ảnh xám của vùng khung (dùng chung cho iOS/Android). Không chụp khi có góc bị cắt, thẻ quá gần / quá xa, hoặc tỉ lệ không đúng thẻ ID-1; lúc đó hiện hướng dẫn "Đưa toàn bộ thẻ vào trong khung".
- Cùng kết luận ở 2 frame liên tiếp trong 1.5 s.
Cài đặt #
dependencies:
zp_cccd_scanner: ^1.3.0
- iOS ≥ 15.5. Thêm vào
Info.plist:<key>NSCameraUsageDescription</key> <string>Cần dùng camera để quét thẻ CCCD</string> - Android minSdk 21, Activity phải là
FlutterActivity/FlutterFragmentActivity.
Sử dụng #
Mỗi CccdScannerView quét đúng một mặt. Luồng eKYC: chụp mặt trước rồi mặt sau, sau đó kiểm tra 2 mặt cùng một thẻ.
final front = await Navigator.push<CccdScanResult>(context, MaterialPageRoute(
builder: (context) => Scaffold(
body: CccdScannerView(
side: CccdSide.front,
onScanned: (r) async => Navigator.pop(context, r),
),
),
));
// ... quét mặt sau tương tự với side: CccdSide.back
switch (CccdPairing.check(front!, back!)) {
case CccdPairStatus.match:
case CccdPairStatus.sameTypeOnly:
// OK
break;
default:
// Hai mặt không cùng một thẻ
}
CccdScanResult gồm: type (mẫu thẻ), side, image (JPEG chứa trọn thẻ), corners (4 góc thẻ), cardRect, pairingKey (khóa nội bộ để ghép 2 mặt), highResolution (ảnh cắt từ ảnh chụp tĩnh độ phân giải cao hay từ frame video). Lib không trích xuất họ tên, ngày sinh...
Chất lượng ảnh #
- Chỉ chụp khi frame đủ nét: camera không đang lấy nét / đổi ống kính và độ nét không kém các frame vừa thấy thẻ.
- Mặc định (
highResolutionImage: true) ảnh thẻ được cắt từ ảnh chụp tĩnh (~12MP trên iOS, độ phân giải cao nhất trên Android); nếu máy không chụp được hoặc thẻ đã xê dịch thì dùng frame video. - iPhone có macro vẫn tự chuyển ống ultra-wide khi thẻ quá gần; lib zoom bù để khoảng cầm máy tự nhiên nằm ngoài vùng chuyển ống.
- Thiếu sáng: camera tự tăng bù sáng, không tự bật đèn (đèn dễ gây lóa mặt thẻ). Dùng
ZpCccdScanner.lowLightđể gợi ý vàZpCccdScanner.setTorch/ZpCccdScanner.torchcho nút đèn. - Gợi ý phụ
CccdScanTip(giữ yên máy, đưa ra xa, thiếu sáng, giấy tờ khác, thẻ bị che) hiển thị trong dòng hướng dẫn mặc định; dùngonTipChangednếu tự làmhintBuilder. - Chặn chụp khi thẻ bị che: nếu ngón tay/vật cản che 1 góc thẻ hoặc che vùng nội dung quan trọng (avatar, mã vạch, QR, dòng MRZ, nhãn thông tin, vân tay — tuỳ loại thẻ), thư viện không tự chụp/nhận diện dù phần còn lại của thẻ vẫn đọc được, báo qua
CccdScanTip.cardObscured. Nếu app cóswitchđầy đủ theoCccdScanTipthì cần thêm case này.
Tùy biến giao diện: guideWidthFactor, guideAlignment, maskColor, idleColor, detectedColor, scanLineColor, scanDuration, hintBuilder, overlay. Bật showDebugOverlay để xem các dòng chữ / mã native đọc được, 4 cạnh thẻ dò được (khung cam) và lý do từ chối của frame gần nhất.
Chụp thủ công khi không nhận diện được #
Nếu người dùng đưa thẻ mãi mà auto-detect không nhận diện được (thẻ mòn, ánh sáng kém...), app có thể tự quyết định khi nào hiện nút "chụp thủ công" (VD sau 15 giây) và để người dùng tự chụp, gửi ảnh lên BE xử lý tiếp — thư viện không tự quyết định thời điểm này, chỉ cung cấp hàm chụp:
final controller = CccdScannerController();
Timer? manualCaptureTimer;
var manualCaptureAvailable = false;
// app tự quản lý đồng hồ đếm giờ + ẩn/hiện nút theo onStateChanged
manualCaptureTimer = Timer(const Duration(seconds: 15), () {
setState(() => manualCaptureAvailable = true);
});
CccdScannerView(
side: CccdSide.front,
controller: controller,
onScanned: (r) async => Navigator.pop(context, r),
onStateChanged: (s) => setState(() => scanState = s),
overlay: manualCaptureAvailable &&
scanState != CccdScanState.verifying &&
scanState != CccdScanState.done
? FilledButton(
onPressed: () async {
final result = await controller.captureManual();
if (result != null) Navigator.pop(context, result);
},
child: const Text('Chụp thủ công'),
)
: null,
)
CccdManualCaptureResult gồm side (mặt đã yêu cầu quét, chưa được xác thực — ảnh chưa qua
OCR), image (JPEG vùng khung hướng dẫn, luôn có), highResolution. Không có type/pairingKey
vì chưa chạy validator — BE tự xử lý tiếp. Chụp thủ công hoạt động ở mọi trạng thái tìm thẻ (kể cả
wrongSide), và không chặn luồng eKYC: có thể chụp thủ công một mặt rồi vẫn quét tự động mặt còn
lại bình thường (lúc đó bỏ qua được CccdPairing.check vì thiếu pairingKey).
Sau khi chụp thành công, view giữ nguyên trạng thái đóng băng cho tới khi app rời màn hình hoặc
gọi controller.resume() (quay lại quét tự động mà không cần mở lại camera).
App demo #
cd example
fvm flutter run
Có 3 lựa chọn: quét riêng mặt trước, quét riêng mặt sau, và luồng eKYC (trước → sau → kiểm tra cùng thẻ).