令和時代の人体錬成

おはこんにちは、Liquid R&Dチームの布目です。

Liquid R&Dチームで現在重点的に取り組んでいることに、eKYCでの顔の真贋判定があります。この記事では真贋判定について簡単に解説します。また真贋判定をより頑強にするための、顔3次元復元+3Dプリンタで人体錬成する取り組みについてまとめました。

真贋判定とは

真贋判定は、本物と偽物を判定するといった意味合いです。顔の真贋判定では、撮影された顔が本物か偽物かを判定することになります。顔の偽物は大雑把に3種類に分けることができます。

  • Photo Attack
    • 偽装したい人の顔写真を紙に印刷して容貌撮影
  • Display Attack
    • 偽装したい人の画像/動画をスマホやディスプレイに表示して容貌撮影
  • Mask Attack
    • 偽装したい人の顔のお面を使って容貌撮影

eKYCでは、例えば不正な手段で入手した他人の顔画像を印刷して、顔撮影を突破しようとすることが想定されます。もし突破されてしまうと、赤の他人で本人確認ができてしまい、銀行口座の開設などができるようになってしまいます。撮影された顔が本物であるか偽物であるか見極めるのはとても大切です。しかしながら、本物は本物と、偽物は偽物と判定する手法はまだまだ発展途上であり多くの研究が行われています。(arxivなどでface anti spoofなどをキーワードに検索するとたくさん論文を読むことができます。)

真贋判定の課題点の一つにデータ集めがあります。上であげたPhoto AttackとDisplay Attackは多大な工数が必要ですが、比較的容易にデータ収集できます。しかしMask Attackはそもそも誰かのお面を作る必要があります。今回は3Dプリンタでお面を作ることを目標に、機材、3次元復元手法について調査しました。最終的には以下のようなサイクルを想定しています!

f:id:liquid-tech:20210705123042p:plain

顔3次元復元について

3つの方向性があります。

  • RGBカメラで複数の角度から撮った数枚の画像から復元
  • RGB-Dカメラで複数の角度から撮った数枚の画像から復元
  • 3Dスキャナで顔形状の復元

RGBカメラとRGB-Dカメラを使ったものを試しました。

RGBカメラ

RGBカメラはスマートフォンデジタルカメラなどに搭載されてるカメラです。いわゆる一般的なカメラですね。

MetaShapeとFaceBuilderというソフトを試しました。

f:id:liquid-tech:20210702123144p:plain f:id:liquid-tech:20210702123154p:plain

MetaShapeはちょっとギザギザした感じ、FaceBuilderはなんとなくスムージングされたモデルを出力しました。

RGB-Dカメラ

RGB-Dカメラは、RGBカメラ同様の画像に追加で深さ(depth)情報を取得できるカメラです。MicrosoftのKincet, Kincet AzureやIntelのReal SenseシリーズなどがRGB-Dカメラにあたります。(AppleのFaceIDでも使われているようです。)

まずはReal Sense d415 + RecFusionというソフトを試しました。

f:id:liquid-tech:20210702123214p:plain f:id:liquid-tech:20210702123240p:plain

ちょっと荒さを感じる結果になりました。

ちょうどこれらを調べている時期にRGB-Dカメラを使ったフォトリアルな3次元復元手法に関する論文が出たのでそちらも試しました。(https://arxiv.org/pdf/2010.05562.pdf) こちらはAzureKinectを使用しています。

f:id:liquid-tech:20210702123300p:plain

なかなかリアルなモデルが出力されて良さそう!でも目がない!となりました。

おわりに

RGB,RGB-Dカメラでの3次元復元を試し、なかなか良いモデル作成が行えることを確認しました。どちらの場合でも言えることですが、高性能なカメラ、正確なカメラキャリブレーションがないとリアルなモデルの作成は難しいです。またリアルなモデルが真贋判定で有利になるのかはまた別に議論が必要な点です。実はちょっとカクカクしているなどの低品質なマスクの方が真贋判定を欺ける可能性も十分に考えられるので、今後も実験を試していきたいところになります。

今回の人体錬成で持って行かれるものはお金と時間だけです!腕と脚は持ってかれないのでとても良心的です!みなさんも気軽に人体錬成しましょう!

Goでスタックトレースを上書きせずにエラーをラップする方法

こんにちは。eKYC開発チームの藤本です。

eKYCのサーバーサイドではpkg/errorsパッケージを使用してGoのスタックトレースを記録しています。スタックトレースは標準のerrorsパッケージではサポートされていませんが、エラー発生時のスタックトレースがわかるとエラーの解決が楽になるので、是非記録しておきたい情報です。

pkg/errorsパッケージを使用してスタックトレースを記録するには、エラーを初期化するときにerrors.New, errors.Errorf関数を使用するか、既存のエラーに対してerrors.WithStack関数を使用します。こうすると、作成されたエラーerrに対してerr.StackTraceメソッドを呼び出すことによりスタックトレースを取得することができ、err.Formatメソッドの出力にもスタックトレースが含まれるようになります。

こうして作成したエラーをラップするとどういう挙動になるでしょうか?

errors.Wrap関数を使用した場合

pkg/errorsには、errors.Wrapというその名の通りにエラーをラップしてくれる関数があるので、これを使用して作成したエラーがどういう挙動をするのかを実際にコードを書いて確認してみます。

コード

package main

import (
    errs "errors"
    "fmt"
    "github.com/pkg/errors"
)

func main() {
    err := errors.New("error")

    we := errors.Wrap(err, "wrap")

    fmt.Println("### Error() ###")
    fmt.Printf("%v\n\n", we.Error())
 
    fmt.Println("### StackTrace() ###")
    if e, ok := we.(interface{ StackTrace() errors.StackTrace }); ok {
        fmt.Printf("%+v\n\n", e.StackTrace())
    }

    fmt.Println("### Formatter ###")
    if e, ok := we.(fmt.Formatter); ok {
        fmt.Printf("%+v\n\n", e)
    }

    fmt.Println("### Unwrap() ###")
    fmt.Printf("%v\n", errs.Is(we, err))
}

出力

### Error() ###
wrap: error

### StackTrace() ###

main.main
    /tmp/sandbox309561315/prog.go:12
runtime.main
    /usr/local/go-faketime/src/runtime/proc.go:225
runtime.goexit
    /usr/local/go-faketime/src/runtime/asm_amd64.s:1371

### Formatter ###
error
main.main
    /tmp/sandbox309561315/prog.go:10
runtime.main
    /usr/local/go-faketime/src/runtime/proc.go:225
runtime.goexit
    /usr/local/go-faketime/src/runtime/asm_amd64.s:1371
wrap
main.main
    /tmp/sandbox309561315/prog.go:12
runtime.main
    /usr/local/go-faketime/src/runtime/proc.go:225
runtime.goexit
    /usr/local/go-faketime/src/runtime/asm_amd64.s:1371

### Unwrap() ###
true

Go Playground

StackTraceメソッドの出力は、errros.Wrap関数を呼び出した時点(12行目)のスタックトレースになってしまい、元々のエラーの発生箇所(10行目)がわからなくなってしまいます。Formatメソッドの出力には、ラップした時点のスタックトレースと元々のスタックトレースが両方出力されるようになります。
標準パッケージのIs関数で元々のエラーとの一致判定を行うことで、作成したエラーがUnwrapメソッドをサポートしていてラップされた状態になっていることも確認しています。(Is関数の仕様)

エラーの原因を特定するためには、元々のエラーの発生箇所を知りたいので、それがわからなくなってしまったり、余分な情報がついて読みづらくなるのはできることなら避けたいところです。

errors.WithMessage関数を使用した場合

命名からは一見わかりませんが、errors.WithMessage関数もエラーをラップしてくれるので、これを使用して作成したエラーの挙動も同様のコードを書いて確認してみます。

コード

package main

import (
    errs "errors"
    "fmt"
    "github.com/pkg/errors"
)

func main() {
    err := errors.New("error")

    me := errors.WithMessage(err, "message")

    fmt.Println("### Error() ###")
    fmt.Printf("%v\n\n", me.Error()) 

    fmt.Println("### StackTrace() ###")
    if e, ok := me.(interface{ StackTrace() errors.StackTrace }); ok {
        fmt.Printf("%+v\n\n", e.StackTrace())
    }

    fmt.Println("### Formatter ###")
    if e, ok := me.(fmt.Formatter); ok {
        fmt.Printf("%+v\n\n", e)
    }

    fmt.Println("### Unwrap() ###")
    fmt.Printf("%v\n", errs.Is(me, err))
}

出力

### Error() ###
message: error

### StackTrace() ###
### Formatter ###
error
main.main
    /tmp/sandbox076104167/prog.go:10
runtime.main
    /usr/local/go-faketime/src/runtime/proc.go:225
runtime.goexit
    /usr/local/go-faketime/src/runtime/asm_amd64.s:1371
message

### Unwrap() ###
true

Go Playground

こちらの出力では、スタックトレースをが上書きされずにラップできていることがわかります。ただし、StackTraceメソッドは使えないため、Formatメソッドの出力結果でスタックトレースを確認する必要があります。

まとめ

エラーをerrors.Wrap関数とerrors.WithMessage関数でラップした場合の挙動を比較してみました。StackTraceメソッドが使えないというデメリットはありますが、errors.WithMessage関数を使うとスタックトレースを上書きせずに簡単にエラーをラップできるので是非使用してみてください。

Liquid が目指すものと技術組織の取り組み

こんにちは、Liquid の CTO の大岩 ( @ooiwa ) です。

Liquid という名前を聞いたことがあるでしょうか? ご存知な方は、生体認証、eKYCという言葉を思い浮かべるかもしれません。僕たちは、LIQUID eKYC というB2B2Cのサービスを作っています。一言でいうと、「従来は窓口に行く必要があった本人確認を、スマートフォンでセルフィー撮影と身分証撮影で済むようにした」サービスです。このブログ開設の日からちょうど2年前にあたる2019年7月1日にリリースされ、金融口座の開設から、携帯電話の契約、C2Cサービスでの本人確認など、幅広く使われています。

それを運営するLiquidは、「認証を空気化し、滑らかな世界をつくる」ことを目指しています。LIQUID eKYC の実現により、このビジョンへ少し近づくことができました。窓口や郵送の必要がなくなり、新しくサービスを利用するまでの時間を短縮できました。

目指すべき世界は更に滑らかなものを想像します。例えば、サービスを利用するまでの時間を短縮できたものの、より待ち時間を少なくできないか。口座作成時のみならずログイン時や端末を変えたときなども、パスワードなしにスムーズに利用ができないか。

f:id:ooiwa:20210630151435p:plain
公式HPでは、我々が抱く未来のイメージを紹介しています。

そこに近づくために、Liquidのエンジニアたち、リサーチャーたちは何に取り組んでいるのか? 第1回テックブログでは、その課題と取り組みの一部を紹介していきます。

LIQUID eKYC を末永く続けるための改善

今日で2周年を迎えたLIQUID eKYCですが、

  • セキュリティルームの構築を含む高いセキュリティ
  • いち早いスマートフォンWEBでの対応
  • リリースから今までの離脱率の改善

他にも多くのことを達成できており、ここまで至れたチームを誇りに思います。

一方で、機能追加と安定性を重視して進んできた結果、運用負荷への対応が後回しになってしまったり、当初の想定を超えた大幅な仕様変更に苦労するところも出てきています。コードベースやパフォーマンスの改善、チームとして取り組めるようなレベルアップと、一歩ずつ先に進んでいくことに取り組んでいます。

そして、このサービスは、金融を含めた様々な業界を支える認証のインフラとして、至極当たり前ですが今後長く存在し続けます。技術の発展に伴い、より楽に使えて、より高セキュリティに成長をし続ける予定です。また僕たちは、eKYCをビジョンの実現のための起点と考えています。本人確認された情報をもとに、様々なサービスが作られていくことを目指しています。データの持ち方のありようや、セキュリティのレベルを離散的に上げられる技術も検討していきます。

f:id:ooiwa:20210630151650p:plain
Miro でのデザイン協議

f:id:ooiwa:20210630151716p:plain
ある日のeKYCバックエンドおしゃべりのメモ

不正検知技術の向上

鍵となる技術の1つが、不正検知技術です。

今のeKYCでは、セルフィーを撮影する際に顔を動かしてもらったり、免許証の斜め面をとってもらったりと、少しユーザーに負担をお願いしています。また、オペレーターの管理画面での確認の時間もかかります。大多数のユーザーには関係ないものの、ごく一部の不正をチェックするためにこのプロセスが存在しています。逆に、不正を検知する技術が上がれば、それぞれの負担を軽減し、今までコストが高くてできなかった用途でも楽に認証ができる可能性があります。

Liquidでは、顔認証、画像処理による偽造顔検知(※1)、端末の使いまわし検知(※2)のあわせ技でこの問題に取り組んでいます。データのあり方の検討から、スマートフォンアプリやWEBでのUIの検討と作成、データの作成、モデルの検証、実データでの評価とループを回しています。

※1 特許出願済 2020-173696 ※2 特許出願済 2020-177094​

f:id:ooiwa:20210630151948p:plain
左から本物(Bonafide)の大岩、ディスプレイ上の大岩、お面の大岩、face swap された大岩

eKYC の次の認証を空気化する新規事業

そして、eKYCで登録された情報をどう活用し、さらなる価値を作り出していくかが、新規事業のチームの課題となります。

eKYCで顔撮影をして口座登録をしたら、ログインや送金時の認証も顔でできないでしょうか。パスワードを忘れても、面倒なやり取りで2,3日待つことなく、安全に即座に復帰ができないでしょうか。登録後のアカウントの引き渡しを防ぐすべはないでしょうか。

  • 法律や、世界の標準化の動きの把握
  • 顔認証や不正検知の技術の精度や振る舞いの見極め
  • 高いセキュリティの担保とユーザーの同意
  • ユーザーや事業者のニーズの開拓

と多くのことを考慮し、さらにこれを高速で検証しなければならず、もう一つスタートアップを作るようなものになっています。

特にここは圧倒的に人が足りないのですが、今中心メンバーとなってくれる方を絶賛募集しています。

おわりに

今回は課題と取り組みの一部を紹介しました。次回からは、様々な情報発信をして、どうこのビジョンに向かっているのか、またどんな人達がいるのかを知ってもらえればと思います。

こんな記事を書く予定です。

  • LIQUID eKYC を支える技術の紹介
  • メンバーが日々学び試している技術の紹介
  • 社内の議論のメタファーとしてよく出る本やアニメの紹介

そんなLiquidでは、一緒に技術のチャレンジをしてくれる仲間を探しています。

  • 目指すべき世界にワクワクする人
  • 技術の取り組みに興味がある人
  • (これからのブログ記事で)中の人に興味を持った人

お話しましょう、ぜひ気軽にこちらからご連絡ください!

liquidinc.asia