📷

PayPayのQR読み取りが優秀過ぎて辛かった話

2023/11/05に公開1

要約

  • PayPayのQR読み取りがすごい。
  • お手軽に試せるライブラリを使ってQRリーダーを実装し、限界性能を調べました。

背景

この間 QR読み取り機能を具備したスマホアプリを作る機会がありました。
OSSで公開されているQRデコーダー使って実装してお客さんに使ってもらったところ、こんなことを言われました。

反応がちょっと悪いよね。PayPayくらい速くならないかな?

PayPayは言わずとしれたQR決済システムです。
僕も普段から使うのですが、たしかにQRコードの読み取りがとんでもなく速いですよね。
どんな実装しているんだろ?とは考えたことなかったのですが、自分が実装する側に回ってみると、とてもじゃないけどPayPayのような性能が出ません。
けれどもお客さんは 「QR読み取りといえばPayPay」くらいの基準になってしまっているため、相対的にUXが悪いと感じてしまっていました。

結果としては、PayPayほどの性能のあるQRリーダーを作るのは困難と判断し、作ったアプリの限界性能を調べて、ちゃんと読み取れる条件を説明することで納得してもらいました。
この取り組みの過程で色々と知見が得られたので、共有させていただきたいと思います。

実装したもの

アプリはFlutterで作りました。
そしてQRデコーダーですが、こちらのライブラリを使わせていただきました。

https://github.com/juliuscanute/qr_code_scanner

このライブラリは非常に使いやすくて、ライブラリから提供される QRView というWidgetを組み込むだけですぐにPayPayのようなQR読み取り画面が実装できます。
作ったアプリでは、カメラの映像ストリームからQRコードが見つかったら、自動的に次の画面に遷移するような実装になっています。画面のサンプルと、最小のコードを載せておきます(コードは一番下です。)。

限界性能の検証

QRコードを読み取る条件を色々と変えて、ギリギリ読み取れる条件を探ります。(定量的な試験はしていませんが、ご容赦ください。)
今回検証した項目は以下です。

項目 内容
ピッチ角 スマホをピッチ角方向に回転させたときに読み取れるギリギリの角度
ロール角 スマホをロール角方向に回転させたときに読み取れるギリギリの角度
距離 QRから徐々にスマホを遠ざけていき、読み取れるギリギリの距離
有効画角 画角内にQRコードがどの程度写っていれば読み取れるか

なお、検証に使用したQRコードは14文字の半角英数字をエンコードしたもので、2cm四方の紙に印刷したものです。ピッチ角、ロール角の補足説明を下のイラストとして載せておきます。

また、アプリをインストールしたスマホは 格安スマホの Huawei Nova lite です。(3年前に購入したときは1万円台だった気がするのですが、値上がりしてますね...。)

https://www.amazon.co.jp/HUAWEI-【日本正規代理店品】-NOVA-LITE-BL/dp/B07WJKHJX2/ref=sr_1_2?crid=1QMRBSLXK2JSW&keywords=huawei%2Bスマホ%2Bnova&qid=1699184411&sprefix=huawei%2Bスマホ%2B%2Caps%2C182&sr=8-2&th=1

結果

ピッチ角

  • かなり傾けても読めています。

ロール角

  • こちらもなかなか読めます。

距離

  • 0.53m が限界距離でした。データ量が増えてくると、潰れが発生するためもう少し短くなると思います。

有効画角

こちらがPayPayとの差異がとくに顕著な項目でした。PayPayにおいては画角内にQRコードが少しでも映り込むと読み取られてしまうのですが、このアプリにおいては画角の端の方にQRがあっても、読み取ってくれません。中央にQRを移して、フォーカスが合うまで待つ時間があることが、PayPayとのUXの違いを産んでいるのではと考えられました。

総評

回転に関してはかなりロバストで、スマホが多少傾いていたところでサクサク読んでくれます。
また格安スマホの画質でかなり小さく写っていても読めるため、大抵のスマホでQRのサイズによる問題が起こることはないのでは、という所感です。
一方で有効画角については注意する必要があって、PayPayのように雑にQRをかざして読み取れないため、「できるだけ中央にQRコードをかざして、静止してください」といった注意喚起をする必要があるかもしれません。
また今回検証した項目以外にも、照明の当たり方やスマホカメラのオートフォーカスの性能にもかなり依存しそうだということがわかったため、今回の結果だけをもって説明するのは不十分なような気もします。(QR読み取りの性能評価手法や指標ってどのようなものがあるのでしょうか?知っている方いたら教えてください。)

まとめ

OSSのライブラリを利用したQRリーダーは、お手軽に実装できるにしてはかなり良い性能で、技術者としては開発者に尊敬の意を表すしかありません。
一方で裏側を知らないお客さんからすると、PayPayのUXを期待するため「ん?」と感じる点が出てきてしまうようです。QRに読み取りにおけるUXがお客さんの業務においてどの程度クリティカルかによるかとは思いますが、開発者としては作ったアプリの性能は調べた上で、できないこととできることを丁寧に説明する必要があると感じました。
ところでPayPayのUXってどうやって実現しているのでしょうか?
QRっぽいところを検知したり、歪んだりぼやけたQRに対してなにかすごい補正をかけていたりするのでしょうか?こちらもなにか知っている方いたらコメントしてほしいです。

最後に

こちらのアプリが使われた事例が以下のNoteの記事で紹介されています。もしよければ読んでください。
https://note.com/r1_technologies/n/n00ff8621dcb9

サンプルコード

実装したアプリのQR読み取り部分のサンプルコードです。

import 'dart:io';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:qr_code_scanner/qr_code_scanner.dart';

class HomeView extends StatefulWidget {
  const HomeView({super.key});

  
  State<HomeView> createState() => _HomeViewState();
}

class _HomeViewState extends State<HomeView> {
  final GlobalKey qrKey = GlobalKey(debugLabel: 'QR');
  Barcode? result;
  QRViewController? controller;
  String? pinCode;

  
  void reassemble() {
    super.reassemble();
    if (Platform.isAndroid) {
      controller!.pauseCamera();
    } else if (Platform.isIOS) {
      controller!.resumeCamera();
    }
  }

  void _onQRViewCreated(QRViewController controller) {
    this.controller = controller;
    controller.scannedDataStream.listen((scanData) {
      controller.stopCamera();
      context.push(
        '/commit',
        extra: {'qrCode': scanData.code, "controller": controller},
      );
    });
  }

  
  void dispose() {
    controller?.dispose();
    super.dispose();
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: Stack(
        children: [
          Column(
            children: <Widget>[
              Expanded(
                flex: 5,
                child: QRView(
                  key: qrKey,
                  onQRViewCreated: _onQRViewCreated,
                ),
              ),
              Expanded(
                flex: 1,
                child: Center(
                  child: (result != null)
                      ? Text(
                          'Barcode Type: ${describeEnum(result!.format)}   Data: ${result!.code}')
                      : const Text('QRコードをスキャンしてください'),
                ),
              )
            ],
          ),
        ],
      ),
    );
  }
}

0.531

Discussion

Dominic_KrDominic_Kr

Thank you.
What's interesting is that there are differences in pitch, rolling, distance, and angle of view from various perspectives
In fact, the process of seeing the differences in each one was so smooth and good.
As shown in the general review, the fact that the QR code falls if it is not located in the center seems to be a part that can be improved enough by image processing after detec.
Technically, how to reconfigure the rotation, tilting, and position of QR code
As a guide, I think it can be improved in many directions, such as providing users with additional guides to dimmed outside the center screen and whether they are trying to recognize it or not. Thank you.