[文章]HarmonyOS教程—基于AI的码生成能力,实现二维码生成与扫码功能

阅读量0
1
4
1. 介绍      
本文档将介绍如何生成常用的二维码、如何识别图片中的二维码、如何实现二维码扫描功能。
场景介绍
  • 医学或健康类应用:根据输入的体温数据,生成对应健康二维码。
  • 社交或通讯类应用:根据公众号图片,识别图中二维码关注公众号。
  • 购物或支付类应用:根据商家提供的付款二维码,扫描后进行支付功能。

2. 搭建HarmonyOS环境
我们首先需要完成HarmonyOS开发环境搭建,可参照如下步骤进行。
  • 安装DevEco Studio,详情请参考下载和安装软件
  • 设置DevEco Studio开发环境,DevEco Studio开发环境需要依赖于网络环境,需要连接上网络才能确保工具的正常使用,可以根据如下两种情况来配置开发环境:
    • 如果可以直接访问Internet,只需进行下载HarmonyOS SDK操作。
    • 如果网络不能直接访问Internet,需要通过代理服务器才可以访问,请参考配置开发环境
  • 开发者可以参考以下链接,完成设备调试的相关配置:

3. 代码结构解读      
工程结构描述如下:
  • qrlibrary
    • core
      GenerateCore:二维码生成核心类。
      ScannerCore:二维码识别核心类。
    • util
      PixelMapUtil:图像处理工具类。
      QRCodeUtil:辅助工具类。
    • CameraPreview:相机预览。
    • QRCodeView:二维码扫描核心类。
    • ScanBoxView:扫描框布局类。
  • slice
    • CodeGenerationAbilitySlice:码生成示例。
    • CodeIdentificationAbilitySlice:码识别示例。
    • CodeScanningAbilitySlice:码扫描示例。
    • MainAbilitySlice:首页页面入口。
  • resources
    • /base/layout
      ability_code_generation.xml:码生成布局。
      ability_code_identification.xml:码识别布局。
      ability_code_scanning.xml:码扫描布局。
      ability_main.xml:首页入口布局。
    • /base/media:icon.png用于生成logo图标的图片,a~d.jpg为码识别需要使用的图片。line_broad.png和line_grid.png图片是自定义ScanBoxView类中使用的边框。
   
4. 二维码生成      
如上图,常用的二维码有中英文二维码,不同颜色二维码和带有logo图标的二维码。
生成中英文二维码先通过IBarcodeDetector类生成二维码图片字节数组,再使用ImageSource类将字节数组解码为PixelMap图像。
步骤 1 -  调用VisionManager.init()方法,建立与能力引擎的连接。定义ConnectionCallback回调,能力引擎连接成功会触发onServiceConnect()方法执行。在此方法中实例化IBarcodeDetector接口。代码如下:
  1. private IBarcodeDetector barcodeDetector;

  2. private void initManager(Context context) {
  3.     ConnectionCallback connectionCallback = new ConnectionCallback() {
  4.                 [url=home.php?mod=space&uid=2735960]@Override[/url]
  5.                 public void onServiceConnect() {
  6.                     barcodeDetector = VisionManager.getBarcodeDetector(context);
  7.                 }
  8.                 @Override
  9.                 public void onServiceDisconnect() {
  10.                 }
  11.             };
  12.     VisionManager.init(context, connectionCallback);
  13. }
复制代码


步骤 2
-  定义码生成字符串的内容content,码生成图像的尺寸size。调用IBarcodeDetector的detect()方法,生成二维码图片字节数组。代码如下:
  1. private byte[] generateQR(String content, int size) {
  2.     byte[] byteArray = new byte[size * size * BYTE_SIZE];
  3.     String val = content;
  4.     try {
  5.         // 解决中文不能识别的问题
  6.         val = new String(content.getBytes("UTF-8"), "ISO8859-1");
  7.     } catch (UnsupportedEncodingException e) {
  8.         QRCodeUtil.error("error");
  9.     }
  10.     barcodeDetector.detect(val, byteArray, size, size);
  11.     return byteArray;
  12. }
复制代码

步骤 3 -  调用ImageSource.create(byte[] data, ImageSource.SourceOptions opts)方法,创建图像源ImageSource对象。其中,data为生成额二维码字节数组,opts为解码参数。然后调用ImageSource的createPixelmap()方法,获取PixelMap图像对象。代码如下:
  1. public PixelMap generateCommonQRCode(String content, int size) {
  2.     byte[] data = generateQR(content, size);
  3.     ImageSource source = ImageSource.create(data, null);
  4.     ImageSource.DecodingOptions opts = new ImageSource.DecodingOptions();
  5.     opts.editable = true;
  6.     PixelMap pixelMap = source.createPixelmap(opts);
  7.     return pixelMap;
  8. }
复制代码

生成不同颜色二维码

默认生成PixelMap图像的颜色是黑色的,像素值为0xFF000000,只需要将其修改为自定义的颜色值。
步骤 1 -  调用PixelMap的getImageInfo()方法可以获取位图的大小,并循环遍历每个像素点。
  1. Size size = pixelMap.getImageInfo().size;
  2. for (int i = 0; i < size.width; i++) {
  3.     for (int j = 0; j < size.height; j++) {
  4.         // 更改像素点的值
  5.     }         
  6. }
复制代码

步骤 2 -  调用PixelMap的readPixel(Position pos)方法读取指定位置的像素值,Position描述为图像坐标。默认生成的像素值为0xFF000000。
  1. int color = pixelMap.readPixel(new Position(i, j));
  2. if (color == 0xFF000000) {
  3.     // 在指定位置写入像素
  4. }
复制代码

步骤 3 -  定义需要更改颜色的变量,然后调用PixelMap的writePixel(Position pos, int color)方法向指定位置写入数据。
  1. public static final int QR_COLOR = 0xFF009900;         
  2. pixelMap.writePixel(new Position(i, j), QR_COLOR);
复制代码

生成logo图标二维码

将logo图标对应的PixelMap图像,画在已经生成二维码PixelMap图像的中间。
步骤 1 -  调用getResourceManager().getResource()方法读取"resources/base/media/"目录下的资源图片,然后得到该资源图片的位图PixelMap对象。
  1. InputStream  resource = getResourceManager().getResource(ResourceTable.Media_icon);
  2. ImageSource logoSource = ImageSource.create(resource, null);
  3. PixelMap logoMap = logoSource.createPixelmap(null);
复制代码

步骤 2 -  获取二维码PixelMap图像的大小,然后将logo的PixelMap图像缩小为0.14。代码如下:
  1. Size size = pixelMap.getImageInfo().size;

  2. PixelMap.InitializationOptions opts =new PixelMap.InitializationOptions();
  3. opts.size = new Size((int) (size.width * 0.14),(int) (size.height * 0.14));
  4. PixelMap logoPixelMap = PixelMap.create(logoMap, opts);
复制代码

步骤 3 -  使用Canvas在二维码PixelMap图像的中间绘制logo的PixelMap图像。代码如下:
  1. Canvas canvas = new Canvas(new Texture(pixelMap));
  2. int centerX = size.width / 2- logoSize.width / 2;
  3. int centerY = size.height / 2- logoSize.height / 2;
  4. canvas.drawPixelMapHolder(new PixelMapHolder(logoPixelMap), centerX, centerY, new Paint());
复制代码
5. 二维码识别
9.png

10.png


上面四张图中的二维码为上一节生成的,现在验证能否被识别出来。其中, 第二张图识别结果为"华为官方网址",其他三张识别结果均是http://www.huawei.com
思路是将图片转换为PixelMap对象,将引入第三方库文件完成二维码识别功能。具体步骤如下:
步骤 1 -  在libs文件夹下引入下图两个.so文件(libiconv.so、libzbarjni.so)和一个.jar包文件(zbar.jar)。

步骤 2 -  将需要识别图片的PixelMap对象数据载入到net.sourceforge.zbar.Image对象中。代码如下:
  1. private Image processImage(PixelMap pixelMap) {
  2.     int pWidth = pixelMap.getImageInfo().size.width;
  3.     int pHeight = pixelMap.getImageInfo().size.height;
  4.     // 初始化装载图像Image对象
  5.     Image barcode = new Image(pWidth, pHeight, "RGB4");
  6.     int[] pix = new int[pWidth * pHeight];
  7.     // 将位图数据写入到Image中
  8.     pixelMap.readPixels(pix, 0, pWidth, new ohos.media.image.common.Rect(0, 0, pWidth, pHeight));
  9.     barcode.setData(pix);

  10.     return barcode.convert("Y800");
  11. }
复制代码
步骤 3 -  初始化二维码识别器ImageScanner对象。代码如下:
  1. private ImageScanner mScanner;

  2. private void initImageScanner() {
  3.     mScanner = new ImageScanner();
  4.     mScanner.setConfig(0, Config.X_DENSITY, CONFIG_SIZE);
  5.     mScanner.setConfig(0, Config.Y_DENSITY, CONFIG_SIZE);
  6. }
复制代码
步骤 4 -  通过调用ImageScanner对象的scanImage()方法,对图像对象Image进行处理,并调用其getResults()方法,得到二维码识别的结果。代码如下:
  1. private String processData(Image barcode) {
  2.     String symData = null;
  3.     if (mScanner.scanImage(barcode) == 0) {
  4.         return symData;
  5.     }
  6.     for (Symbol symbol : mScanner.getResults()) {
  7.         // 未能识别的格式继续遍历
  8.         if (symbol.getType() == Symbol.NONE) {
  9.             continue;
  10.         }
  11.         symData = new String(symbol.getDataBytes(), StandardCharsets.UTF_8);
  12.         // 空数据继续遍历
  13.         if (QRCodeUtil.isEmpty(symData)) {
  14.             continue;
  15.         }
  16.         return symData;
  17.     }
  18.     return symData;
  19. }
复制代码

6. 二维码扫描  
上图所示,开启摄像头来扫描二维码并识别结果。思路是采集相机的数据转为PixelMap对象,然后使用码识别的功能。

开启摄像头
步骤 1-  继承SurfaceProvider类,在构造函数调用getSurfaceOps().get().addCallback(this)这行代码,在其实现方法surfaceCreated()中,得到Surface对象,用来展示摄像头窗口。代码如下:
  1. public class CameraPreview extends SurfaceProvider implements SurfaceOps.Callback {
  2.     private Surface previewSurface;

  3.     public CameraPreview(Context context) {
  4.         super(context);
  5.         getSurfaceOps().get().addCallback(this);
  6.     }

  7.     @Override
  8.     public void surfaceCreated(SurfaceOps surfaceOps) {
  9.         previewSurface = surfaceOps.getSurface();
  10.     }
  11. }
复制代码
步骤 2 -  通过CameraKit.getInstance()方法得到CameraKit对象,调用CameraKit的createCamera()方法,创建相机对象。代码如下:
  1. private void openCamera() {
  2.     CameraKit cameraKit = CameraKit.getInstance(getContext());
  3.     String[] cameraIds = cameraKit.getCameraIds();
  4.     cameraKit.createCamera(cameraIds[0], new CameraStateCallbackImpl()
  5.             new EventHandler(EventRunner.create("qr")));
  6. }
复制代码
步骤 3-  定义CameraStateCallback实现类,相机对象Camera创建成功会回调其onCreated()方法。然后调用CameraConfig.Builder的addSurface()方法,配置相机Surface预览画面。代码如下:
  1. private class CameraStateCallbackImpl extends CameraStateCallback {
  2.     @Override
  3.     public void onCreated(Camera camera) {
  4.         super.onCreated(camera);
  5.         CameraConfig.Builder cameraBuilder = camera.getCameraConfigBuilder();

  6.         cameraBuilder.addSurface(previewSurface);

  7.         camera.configure(cameraBuilder.build());
  8.     }
  9. }
复制代码

数据采集与解析
使用ImageReceiver类来接收相机每帧数据,然后将每帧数据转化为PixelMap对象。具体思路如下:
步骤 1-  定义ImageReceiver.IImageArrivalListener类,在onImageArrival()方法中处理每一帧数据。代码如下:
  1. private ImageReceiver.IImageArrivalListener imageArrivalListener = new ImageReceiver.IImageArrivalListener() {
  2.     @Override
  3.     public void onImageArrival(ImageReceiver imageReceiver) {
  4.         // 每帧数据图像接收
  5.     }
  6. };
复制代码
步骤 2-  初始化ImageReceiver,并设置图像回调类IImageArrivalListener,第三个参数格式要配置为ImageFormat.JPEG格式。代码如下:
  1. ImageReceiver imageReceiver = ImageReceiver.create(SCREEN_HEIGHT, SCREEN_WIDTH, ImageFormat.JPEG, IMAGE_RCV_CAPACITY);
  2. imageReceiver.setImageArrivalListener(imageArrivalListener);
复制代码
步骤 3-  调用ImageReceiver的getRecevingSurface()方法得到Surface对象,将其配置在CameraConfig.Builder中,用来接收相机的每一帧数据。代码如下:
  1. @Override
  2. public void onCreated(Camera camera) {
  3.     super.onCreated(camera);
  4.     CameraConfig.Builder cameraBuilder = camera.getCameraConfigBuilder();
  5.     Surface imageSurface = imageReceiver.getRecevingSurface();
  6.     cameraBuilder.addSurface(imageSurface);
  7. }
复制代码
步骤 4-  调用ImageReceiver的readNextImage()得到ohos.media.image.Image图像,然后调用ohos.media.image.Image.Component类的read()方法,读取图像数据到字节数组中。代码如下:
  1. @Override
  2. public void onImageArrival(ImageReceiver imageReceiver) {
  3.     ohos.media.image.Image image = imageReceiver.readNextImage();
  4.     ohos.media.image.Image.Component component = image.getComponent(ImageFormat.ComponentType.JPEG);
  5.     byte[] data = new byte[component.remaining()];
  6.     component.read(data);
  7. }
复制代码
步骤 5-  最后使用ImageSource解码字节数组,得到PixelMap对象。代码如下:
  1. ImageSource.SourceOptions sourceOptions = new ImageSource.SourceOptions();
  2. sourceOptions.formatHint = "image/jpg";
  3. ImageSource imageSource = ImageSource.create(data, sourceOptions);
  4. PixelMap pixelMap = imageSource.createPixelmap(null);
复制代码

7. 恭喜你   
通过本教程的学习,你已学会了如何生成常用二维码、识别二维码、实现二维码扫描。
主要技术包括:
  • EventHandler在子线程执行任务。
  • 如何引入及使用.so文件。
  • PixelMap位图解码使用。
  • 相机数据采集与PixelMap转化。
   
8. 参考      Gitee源码
   



回帖

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容图片侵权或者其他问题,请联系本站作侵删。 侵权投诉
链接复制成功,分享给好友