
Seja bem vindo(a) a segunda parte do artigo “como criar um APP Mobile (usando React Native + Express + Tesseract.js) que vai utilizar OCR para extração de textos de fotos tiradas com o APP“.
Na primeira parte nós desenvolvemos a API usando Express que recebe uma imagem como parâmetro e devolve os textos presentes nela via Json, nesta parte nós desenvolveremos um aplicativo com React Native que se irá se conectar a API para converter as frases das fotografias tiradas.
Caso você ainda não tenha concluída a parte 1, siga este tuturial antes de prosseguir, caso já tenha feito, venha com a gente finalizar esse APP incrível 😁
Obs: Já deixa um comentário e compartilha esse artigo se você estiver gostando desta série 💪
Nossas ferramentas
- Node.js
- Yarn (ou npm)
- React Native (Expo)
- Ngrok
- Código completo API Express: https://github.com/escola-de-javascript/react-native-ocr
- Código completo Cliente React Native (expo): https://github.com/escola-de-javascript/react-native-ocr-cliente-expo
Para facilitar a criação do nosso App mobile com React Native nós vamos utilizar uma ferramenta chamada expo, com ela nós não precisaremos instalar o android studio para criar APPs para o android ou o xcode para criar APPs para o IOS como é de costume no React Native.
Além disso ela possui um APP mobile para que nós possamos testar facilmente os APPs desenvolvidos com ela, basta subir o servidor, apontar o APP para o QR Code apresentado e ver tudo funcionando.
Antes de prosseguir, baixe o APP do expo na Play Store ou Apple Store no seu mobile.
Preparando o Projeto
Vamos instalar o Expo, gerar um novo projeto utilizando ele e ver tudo funcionando no nosso mobile.
1 – Instale o react native rodando:
1 |
npm install -g expo-cli |
- Este comando adiciona ao terminal (ou powershell) a CLI do expo (Command Line Interface ou interface de linha de comandos) desta forma poderemos chamar o comando expo no terminal.
2 – Crie o projeto rodando:
1 |
expo init ocr_client |
- Ao rodar expo init o expo cria um projeto básico.
3 – Entre na pasta do projeto:
1 |
cd ocr_client |
4 – Apenas para testarmos, coloque no App.js:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
import React from 'react'; import { StyleSheet, Text, View } from 'react-native'; export default function App() { return ( <View style={styles.container}> <Text>Hello Escola de Javascript s2</Text> </View> ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#fff', alignItems: 'center', justifyContent: 'center', }, }); |
5 – Suba o projeto rodando:
1 |
yarn start |
6 – O expo vai abrir o projeto no browser e te dar um QR code, usando o APP dele, escaneie o código e veja o APP no seu mobile:
7 – Pronto, projeto criado, agora podemos seguir o desenvolvimento o/
Adicionando o componente da câmera
Para usar a câmera em nosso projeto, usaremos o component de câmera do expo, nesta parte vamos instalar ele e configurar o uso no nosso APP.
1 – Instale o component rodando:
1 |
expo install expo-camera |
2 – Para vermos o component em funcionamento vamos integrar ele no nosso App.js e permitir que o nosso APP mostre a câmera do celular, coloque no arquivo App.js:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
import React from 'react'; import { Text, View, TouchableOpacity } from 'react-native'; import * as Permissions from 'expo-permissions'; import { Camera } from 'expo-camera'; import { Ionicons} from '@expo/vector-icons'; export default class App extends React.Component { state = { hasCameraPermission: null, type: Camera.Constants.Type.back, }; async componentDidMount() { const { status } = await Permissions.askAsync(Permissions.CAMERA); this.setState({ hasCameraPermission: status === 'granted' }); } takePicture = async () => { if (this.camera) { // Configuração da câmera const options = { quality: 0.5, base64: true }; const data = await this.camera.takePictureAsync(options); console.log(data) } }; render() { const { hasCameraPermission } = this.state; if (hasCameraPermission === null) { return <View />; } else if (hasCameraPermission === false) { return <Text>No access to camera</Text>; } else { 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: 0.1, alignSelf: 'flex-end', alignItems: 'center', }} onPress={() => { this.setState({ type: this.state.type === Camera.Constants.Type.back ? Camera.Constants.Type.front : Camera.Constants.Type.back, }); }}> <Text style={{ fontSize: 18, marginBottom: 10, color: 'white' }}> Flip </Text> </TouchableOpacity> </View> <View style={{ flex: 0, flexDirection: "row", justifyContent: "center" }}> <TouchableOpacity onPress={this.takePicture} style={{ alignSelf: 'center' }}> <Ionicons name="ios-radio-button-on" size={70} color="white" /> </TouchableOpacity> </View> </Camera> </View> ); } } } |
- Primeiro incluímos as dependências:
– import { Camera } from ‘expo-camera’ : O component de camera que nos permiti tirar fotografias– import * as Permissions from ‘expo-permissions’ : Para solicitarmos a permissão na hora de usar a câmera– import { Ionicons} from ‘@expo/vector-icons’ : O Ionicons para utilizarmos os ícones no nosso APP (no caso o do botão da câmera)
- Depois adicionamos no state o hasCameraPermission e o type, o primeiro para mostrar a tela da câmera apenas se tivermos a permissão e o segundo para selecionar a câmera da frente ou de trás
- No componentDidMount solicitamos as permissões quando o APP inicia.
- No takePicture tiramos a foto (quando o botão é clicado) e transformamos em base 64 (versão em caracteres da imagem)
3 – Suba o servidor:
1 |
yarn start |
4 – Acesso o APP pelo seu mobile como fizemos anteriormente (se necessário atualize a exibição do APP), você deve ver:
5 – Pronto, já realizamos a integração com a câmera.
Iniciando o Ngrok
Para que nosso APP consiga se conectar a API precisamos usar o ngrok que nos fornecerá um link público de acesso a nossa aplicação local, ao realizar esta seção será necessário o código desenvolvido na parte 1.
1 – Instale o Ngrok.
//Ubuntu
1 |
sudo snap install ngrok |
//Windows
1 |
https://ngrok.com/download |
2 – Em outro terminal entre no projeto da API
3 – Suba o projeto rodando:
1 |
yarn start |
4 – Em um outro novo terminal, inicie o ngrok:
// Ubuntu
1 |
ngrok http 3000 |
// Windows
1 2 |
// Copie o executável do ngrok para a pasta do projeto e rode no powershell ./ngrok http 3000 |
5 – Copie e guarde a url HTTPS que o ngrok fornecer (usaremos em breve):
6 – Pronto, agora nossa API já está disponível.
Conectando à API
Agora iremos conectar à API que criamos na parte 1 do artigo com o nosso APP React Native (expo):
1 – Instale o axios ao nosso cliente React Native:
1 |
yarn add axios |
2 – Crie a pasta “api”, nela colocaremos os arquivos relacionados a nossa api:
1 |
mkdir api |
3 – Crie o arquivo api/ocr.js com o seguinte código:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
import axios from 'axios'; // Cria o corpo da requisição adicionando o arquivo const createFormData = (photo) => { let data = new FormData(); data.append("image", { uri: photo.uri, name: 'image.jpg', type: 'image/jpeg' }); return data; }; export async function handleUpload(uri) { // Corpo da requisição const bodyData = createFormData(uri); // Instância do axios realizando requisição a api const text = await axios({ url: "<API_URL> /upload", method: "post", data: bodyData, // Configura os headers para aceitarem arquivos config: { headers: { 'Content-Type': 'multipart/form-data' } } }).then(response => { return response; }) .catch(error => { return { data: "falha ao processar texto" }; }); // Retorna o texto processado ou mensagem de erro return text.data; }; |
- Para enviar imagens através de uma api usando “form-data” precisamos criar um objeto contento o corpo dos dados a serem enviados, a função createFormData realiza essa função, recebendo o dado da foto e transformando isso em um objeto FormData.
4 – Substitua api_url pela url fornecida pelo ngrok.
Extraindo textos da imagem
Nosso APP já consegue tirar fotos e se conectar a API criada, agora adicionaremos a interação entre a interface e a API.
1 – Altere o arquivo App.js para o seguinte conteúdo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
import React from 'react'; import { Text, View, TouchableOpacity, Alert } from 'react-native'; import * as Permissions from 'expo-permissions'; import { Camera } from 'expo-camera'; import { Ionicons} from '@expo/vector-icons'; import { handleUpload } from './api/ocr'; export default class App extends React.Component { state = { hasCameraPermission: null, type: Camera.Constants.Type.back, }; async componentDidMount() { const { status } = await Permissions.askAsync(Permissions.CAMERA); this.setState({ hasCameraPermission: status === 'granted' }); } takePicture = async () => { if (this.camera) { // Configuração da câmera const options = { quality: 1, base64: true }; const data = await this.camera.takePictureAsync(options); const text = await handleUpload(data); Alert.alert("Texto Extraído", text.data.trim()); } }; render() { const { hasCameraPermission } = this.state; if (hasCameraPermission === null) { return <View />; } else if (hasCameraPermission === false) { return <Text>No access to camera</Text>; } else { 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: 0.1, alignSelf: 'flex-end', alignItems: 'center', }} onPress={() => { this.setState({ type: this.state.type === Camera.Constants.Type.back ? Camera.Constants.Type.front : Camera.Constants.Type.back, }); }}> <Text style={{ fontSize: 18, marginBottom: 10, color: 'white' }}> Flip </Text> </TouchableOpacity> </View> <View style={{ flex: 0, flexDirection: "row", justifyContent: "center" }}> <TouchableOpacity onPress={this.takePicture} style={{ alignSelf: 'center' }}> <Ionicons name="ios-radio-button-on" size={70} color="white" /> </TouchableOpacity> </View> </Camera> </View> ); } } } |
- Na função takePicture perceba que adicionamos um await tanto para a ação de tirar a foto quanto para o envio da foto a nossa api ao chamar a função handleUpload, isso é necessário porque uma requisição não é algo instantâneo, se não colocarmos o await as variáveis conteriam apenas <Promise pending>.
Incluindo um ícone de loading
Para melhorar a experiencia do usuário no nosso pequeno APP vamos adicionar um spinner que vai ficar rodando enquanto a foto é processada pela nossa API Express.
1 – Instale a biblioteca rodando:
1 |
yarn add react-native-loading-spinner-overlay |
2 – Altere o App.js para o seguinte conteúdo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
import React from 'react'; import { Text, View, TouchableOpacity, Alert } from 'react-native'; import * as Permissions from 'expo-permissions'; import { Camera } from 'expo-camera'; import { Ionicons} from '@expo/vector-icons'; import { handleUpload } from './api/ocr'; import Spinner from 'react-native-loading-spinner-overlay'; export default class App extends React.Component { state = { hasCameraPermission: null, type: Camera.Constants.Type.back, spinner: false }; async componentDidMount() { const { status } = await Permissions.askAsync(Permissions.CAMERA); this.setState({ hasCameraPermission: status === 'granted' }); } takePicture = async () => { if (this.camera) { this.setState({ spinner: true }); // Configuração da câmera const options = { quality: 0.5, base64: true }; const data = await this.camera.takePictureAsync(options); const text = await handleUpload(data); Alert.alert("Texto Extraído", text.data.trim()); // Após processar a foto desativa o spinner e mostra o resultado this.setState({ spinner: false }); } }; render() { const { hasCameraPermission } = this.state; if (hasCameraPermission === null) { return <View />; } else if (hasCameraPermission === false) { return <Text>No access to camera</Text>; } else { return ( <View style={{ flex: 1 }}> <Spinner visible={this.state.spinner} textContent={'Processando Imagem... 😉'} textStyle={{ color: '#FFF' }} /> <Camera style={{ flex: 1 }} type={this.state.type} ref={ref => {this.camera = ref;}}> <View style={{ flex: 1, backgroundColor: 'transparent', flexDirection: 'row', }}> <TouchableOpacity style={{ flex: 0.1, alignSelf: 'flex-end', alignItems: 'center', }} onPress={() => { this.setState({ type: this.state.type === Camera.Constants.Type.back ? Camera.Constants.Type.front : Camera.Constants.Type.back, }); }}> <Text style={{ fontSize: 18, marginBottom: 10, color: 'white' }}> Flip </Text> </TouchableOpacity> </View> <View style={{ flex: 0, flexDirection: "row", justifyContent: "center" }}> <TouchableOpacity onPress={this.takePicture} style={{ alignSelf: 'center' }}> <Ionicons name="ios-radio-button-on" size={70} color="white" /> </TouchableOpacity> </View> </Camera> </View> ); } } } |
Testando o Projeto
1 – Suba o servidor:
1 |
yarn start |
2 – Acesse o APP no seu mobile (como anteriormente) e tire uma foto de algum texto curto (em um papel, parede, exceto tela do pc):
- A iluminação, posição e etc vão influenciar na qualidade da extração do texto.
- Não ficou perfeito mas conseguiu extrair os textos presentes 😁
3 – Pronto, nosso APP está totalmente funcional, parabéns por chegar até aqui o/
obs: Caso você tenha algum problema com o tamanho do arquivo, você pode configurar isto no arquivo config/storage.js da API.
Conclusão
Nesse artigo nós criamos um APP com React Native (expo) que consumiu uma API construída com Express e Tesseract (OCR), conseguimos finalizar nosso APP que tira fotos e extrai os textos com sucesso 😁
É possível fazer melhorias na extração dos textos através de uma configuração mais precisa do Tesseract e da imagem que está sendo enviada para a API, vale a pena pesquisar um pouco e melhorar o APP.
Se você gostou deste tutorial deixe um comentário (para que nós possamos criar mais) e compartilha ele com seus amigos e nos seus grupos de programação, muito obrigado por estar com a gente.
Grande abraço
Bem legal e bem didático.
Muito obrigado Marcio 🙂
Bom seria se tivesse algum tipo de opção onde tivesse exemplo: 500 imagens cadastrada numa DB, ai tirariamos a foto, ele rastreava essa foto na db, e dizia o resultado com uma tag ou um texto do titulo da imagem
Dá pra fazer também 🙂
como ?
Usando o Google Vision por exemplo: https://cloud.google.com/vision/
Muito bom, ta de parabéns, seria possível adaptar par o próprio app ler as respostas de um gabarito ?
Obrigado João 🙂
É possível fazer isso, mas você precisa de um esforço relativamente maior.
Não consigo abrir o aplicativo no celular. Utilizei um Qr code normal teria que instalar algum app específico?
Consegui iniciar o aplicativo, porém aparece um erro de “Possible Unhabdled Promise Rejection (id:0): TypeError: undefined is not an object (evaluating ‘text.data.trim’)”. Algume pode me ajudar ?
Olá Gabriela, tudo bem?
Pelo erro, parece que o software não está conseguindo extrair o texto da imagem (por isto está retornando null)
Na parte (text.data.trim) tente deixar apenas (text) para verificar o retorno.
Teria como utilizar ocr sem utilização de servidor, fazer totalmente local no celular? Alguma dica pra extrair texto utilizando recursos que podem ficar totalmente internos no celular?
Boa noite,
Existe a possibilidade de fazer o app totalmente independente de servidor? Apenas com aplicação mobile?
Obrigada.