JavaScript
React
reactnative
expo

ReactNativeでExpoのカメラを使ってみた。

前書き

React Nativeで、カメラ機能を用いてAmazon Rekognitionで画像認識をやってみよう!
という興味本位から、とりあえず、React Nativeでカメラを動かしてみたので
そのまとめ記事になります。
ほとんど、ExpoのCameraのドキュメントを読んだだけです。

作ったものは、publishしていますので、確認していただければ嬉しいです。
https://expo.io/@a.kujou/cognition

Githubはこちらです。
https://github.com/ScenicFlighter/cognition

前提

Expoを用いて開発しますので、最初のテンプレート部分に関しては説明を省いて、カメラ部分の解説をします。
基本的に、create-react-native-app で作成されるテンプレートをもとに、解説していきます。

参考
https://qiita.com/somebody_gp/items/e018cd4a35a72334b9bb

カメラ撮影時に、NativeBaseの通知を追加しているので、インストールしてください。

# npm
npm install native-base
# yarn
yarn add native-base

ゴール

アプリを起動したら、カメラプレビューが表示されて、カメラボタンで撮影して、キャッシュディレクトリに画像が保存されるという部分まで解説します。
やっぱり、割と簡単でした。
以下のような動きをします(ほんとうにこれだけです)

way.gif

Source

とりあえず、未完成ですが、カメラプレビュー部分のソースです。
ボタンを押したらiOSのカメラが動く、ではなく、独自的にカメラを表示する的な形になります。

下記のファイルは、とりあえずcomponentsフォルダ以下に配備します。

/**
 * Camera.js
 * Camera Component
 */
import React from 'react';
import { Text, View, TouchableOpacity } from 'react-native';
import { Toast } from 'native-base';
import { Camera, Permissions, Icon } from 'expo';

import Colors from '../constants/Colors';

export default class CameraComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      hasCameraPermission: null, // カメラ機能の許可
      type: Camera.Constants.Type.back, // 背面カメラを利用
    };

    this.takePicture = this.takePicture.bind(this);
  }

  // 初期起動時、カメラの使用の権限を取得する。
  async componentWillMount() {
    const { status } = await Permissions.askAsync(Permissions.CAMERA);
    this.setState({
      hasCameraPermission: status === 'granted',
    });
  }

  // 撮影
  async takePicture() {
    const pictureData = await this.camera.takePictureAsync();
    Toast.show({
      text: 'Success',
      buttonText: 'Okay',
      duration: 2000,
    });
    console.log(pictureData.uri);
  }

  render() {
    const {
      hasCameraPermission,
    } = this.state;

    if (hasCameraPermission === null || hasCameraPermission === false) {
      return (
        <View>
          <Text>
            カメラの使用が許可されていません。
          </Text>
        </View>
      );
    }
    return (
      <View
        style={{
          flex: 1,
        }}
      >
        <Camera
          style={{
            flex: 1,
          }}
          type={this.state.type}
          ref={(ref) => {
            this.camera = ref;
          }}
        >
          <View
            style={{
              flex: 1,
              backgroundColor: 'transparent',
              flexDirection: 'row',
            }}
          >
            <TouchableOpacity
              style={{
                flex: 1,
                alignSelf: 'flex-end',
                alignItems: 'center',
              }}
              onPress={() => {
                this.takePicture();
              }}
            >
              <Icon.MaterialIcons
                name="camera"
                size={70}
                style={{ marginBottom: 20 }}
                color={Colors.tabIconDefault}
              />
            </TouchableOpacity>
          </View>
        </Camera>
      </View>
    );
  }
}

解説

コード解説

簡単に、重要な部分を解説していきます。

カメラ使用許可

  // 初期起動時、カメラの使用の権限を取得する。
  async componentWillMount() {
    const { status } = await Permissions.askAsync(Permissions.CAMERA);
    this.setState({
      hasCameraPermission: status === 'granted',
    });
  }

上記コードは、初期レンダリング前に発火されます。
expoのPermissionsのaskAsync(Permissions.CAMERA)を起動することによって、ユーザに対して
「カメラを使用しますが、よろしいですか?」というダイアログが表示されます(よく見るやつですね)
返り値として、許可された場合は、文字列で「granted」が返ってきます。

その返り値を、hasCameraPermissionにtrueか、falseとして、state管理します。
拒否された場合、カメラの利用ができないため、renderの中で判定して
「カメラの使用を許可してください」など、警告表示の判定ができます。

撮影、画像のキャッシュ保存

撮影ボタンの押下時の処理です。

  // 撮影
  async takePicture() {
    const pictureData = await this.camera.takePictureAsync();
    Toast.show({
      text: 'Success',
      buttonText: 'Okay',
      duration: 2000,
    });
    console.log(pictureData.uri); // キャッシュ保存先パス
  }

ExporのCameraにある、takePictureAsync()を利用することで、撮影(撮影音あり)を行い
画像をキャッシュディレクトリに保存できます。
そのあとに、NativeBaseのToastで簡単に通知を表示させます。

takePictureAsyncの返り値には、uriがあり、そこから撮影した写真を拾い上げることができます。

画面表示

create-react-native-appでテンプレートを作成すると
Home Links SettingsというTabが作成される形になります。
今回は、そのHomeタブで表示される画面全体をカメラ画面にします。
ごっそり、元のファイルからいらない部分を消して、CameraComponentを表示するようにしています。

/// screen/HomeScreen.js
import React from 'react';
import {
  StyleSheet,
  View,
} from 'react-native';

import CameraComponent from '../components/Camera'; // 新規作成ファイル


export default class HomeScreen extends React.Component {
  static navigationOptions = {
    header: null,
  };

  render() {
    return (
      <View style={styles.container}>
        <CameraComponent {...this.props} />
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
  },
});

これで、アプリ起動時にカメラ画面になります。

後書き

ほとんど、Expoの公式を見て、とりあえずカメラを動かしてみただけ、みたいな記事ですが
カメラを起動するといったNative機能も、簡単にReactの知識で動かせるので、いい感じですね。
(個人的に、権限回りの処理(Permission)が直感的ですごく、わかりやすいです。)

まだまだ、実務前身で、ディレクトリの構成とかを考えていないので、とりあえずテンプレートを適当にいじってカメラを動かしてみたという感じになりました。

撮影した画像はキャッシュディレクトリから参照できるので、それをS3にアップロード => Lambdaキック => Rekognitionで認識という流れを作ってみようと思います。