특화 프로젝트에서 나를 가장 고생시킨 Base64인코딩,
Google Cloud Vision API에 이미지 URI를 보내줘도 읽지 못하는데
공식 문서와 똑같이 코드를 작성해도 마찬가지였다.
해답은 base64 encoding을 true로 바꿔주는 것.
하지만 정확히 어떤 형태의 코드로 적용해야 하는 지 알 수 없어
StackOverFlow, GitHub, 이름 모를 영어, 중국어 사이트들을 주말 이틀 동안 헤멘 후에야
Google Cloud Vision API의 OCR 인식 기술을 사용할 수 있었다.
그런데 Base64 인코딩이 뭐길래? 궁금해서 조사해 봤다.
소스코드(.jsx)
import * as ImagePicker from 'expo-image-picker';
import { Camera } from 'expo-camera';
import { Text, View, ScrollView, Image, Alert, TouchableOpacity } from 'react-native';
import React, { useEffect, useState } from 'react';
import Button from '../../components/Button';
import styled from 'styled-components/native';
import { GOOGLE_CLOUD_VISION_API_KEY } from '@env';
// https://cloud.google.com/vision/docs/ocr?apix_params=%7B%22resource%22%3A%7B%22requests%22%3A%5B%7B%22features%22%3A%5B%7B%22type%22%3A%22TEXT_DETECTION%22%7D%5D%2C%22image%22%3A%7B%22source%22%3A%7B%22imageUri%22%3A%22gs%3A%2F%2Fcloud-samples-data%2Fvision%2Focr%2Fsign.jpg%22%7D%7D%7D%5D%7D%7D#vision_text_detection-nodejs
function OCRScreen({ navigation, route }) {
const [cameraPermission, setCameraPermission] = useState(null);
const [response, setResponse] = useState(null);
const [loadMessage, setLoadMessage] = useState('Pick an image');
const [image, setImage] = useState(null);
const [isLoading, setLoading] = useState(true);
const [data, setData] = useState([]);
const [camera, setCamera] = useState(null);
const [imageUri, setImageUri] = useState(null);
const [type, setType] = useState(Camera.Constants.Type.back);
const [isCamera, setIsCamera] = useState(true);
const [isResponse, setIsResponse] = useState(false);
const bookId = route.params.bookId;
// 카메라 권한 허용
const permissionFunction = async () => {
const cameraPermission = await Camera.requestCameraPermissionsAsync();
setCameraPermission(cameraPermission.status === 'granted');
if (cameraPermission.status !== 'granted') {
Alert.alert('Permission for media access needed.');
}
};
useEffect(() => {
permissionFunction();
}, []);
// Google Cloud Vision API 호출
const callGoogleApi = async (base64) => {
setResponse(null);
setLoadMessage('Loading...');
try {
await fetch(`https://vision.googleapis.com/v1/images:annotate?key=${GOOGLE_CLOUD_VISION_API_KEY}`, {
method: 'POST',
body: JSON.stringify({
requests: [
{
image: {
content: base64,
},
features: [{ type: 'TEXT_DETECTION' }],
},
],
}),
})
.then((res) => res.json())
.then((res) => {
setResponse(res.responses[0].textAnnotations[0]);
});
} catch (e) {
console.log(e);
setLoadMessage('Cloud Vision API에서 오류가 발생했어요');
}
};
// 사진 촬영
const takePicture = async () => {
if (camera) {
const options = { quality: 0.5, base64: true };
const data = await camera.takePictureAsync(options);
setImageUri(data.uri);
callGoogleApi(data.base64);
setIsCamera(false);
}
};
// 이미지 선택
const handleClick = async () => {
let result = await ImagePicker.requestMediaLibraryPermissionsAsync();
if (result.granted === false) {
Alert.alert('Need permission to Camera Roll for this to work');
return;
}
// base64로 인코딩
const options = { base64: true };
// 선택된 이미지를 인코딩하여 변수에 저장
let pickedImage = await ImagePicker.launchImageLibraryAsync(options);
if (pickedImage.cancelled === true) {
setIsCamera(true);
return;
}
// Image state 업데이트
setImage(pickedImage);
// cloud vision api 호출
callGoogleApi(pickedImage.base64);
setIsCamera(false);
};
return (
<ScrollView
style={{
backgroundColor: '#FCF9F0',
flex: 1,
}}
>
{isCamera ? (
<Camera
ref={(ref) => setCamera(ref)}
style={{ width: '100%', height: 400 }}
type={type}
ratio={'1:1'}
>
<View
style={{
flex: 1,
backgroundColor: 'transparent',
flexDirection: 'row',
}}
></View>
<TouchableOpacity
style={{
alignSelf: 'flex-end',
alignItems: 'center',
backgroundColor: '#A8CA47',
borderRadius: 10,
margin: 10,
padding: 10,
borderColor: 'black',
borderWidth: 1,
}}
onPress={takePicture}
>
<Text style={{ fontSize: 14, color: 'white', fontFamily: 'Medium' }}>문장 찍기</Text>
</TouchableOpacity>
<TouchableOpacity
style={{
alignSelf: 'flex-end',
alignItems: 'center',
backgroundColor: '#A8CA47',
borderRadius: 10,
margin: 10,
padding: 10,
borderColor: 'black',
borderWidth: 1,
}}
onPress={handleClick}
>
<Text style={{ fontSize: 14, color: 'white', fontFamily: 'Medium' }}>내 갤러리</Text>
</TouchableOpacity>
</Camera>
) : (
<View
styles={{
flex: 1,
backgroundColor: '#FCF9F0',
alignItems: 'center',
justifyContent: 'center',
}}
>
<Image
style={{
width: 300,
height: 300,
marginLeft: 30,
borderRadius: 10,
borderColor: 'black',
borderWidth: 1,
}}
source={{
uri: imageUri,
}}
/>
<View
style={{
marginHorizontal: 20,
backgroundColor: '#FCF9F0',
}}
>
<TouchableOpacity
onPress={() => {
navigation.navigate('OCRRecordCreate', {
OCRText: response.description,
bookId: bookId,
});
}}
>
<View>
<OCRScrollView
style={{
backgroundColor: 'white',
}}
>
<View style={{ padding: 10 }}>
{(response && (
<Text
style={{
fontFamily: 'Medium',
}}
>
{response.description}
</Text>
)) || (
<Text
style={{
fontFamily: 'Medium',
}}
>
{loadMessage}
</Text>
)}
</View>
</OCRScrollView>
</View>
</TouchableOpacity>
</View>
</View>
)}
</ScrollView>
);
}
const OCRScrollView = styled.ScrollView`
margin-top: 20px;
margin-bottom: 20px;
padding: 10px;
border: 1px solid #000;
border-radius: 5px;
width: 100%;
height: 200px;
`;
export default OCRScreen;
Base64 인코딩이란?
Base64란 곧 64진법이라는 뜻이다.
컴퓨터 분야에서는 8비트 이진 데이터를 문자 코드에 영향을 받지 않는 공통 ASCII 영역의 문자들로만 이루어진 일련의 문자열로 바꾸는 인코딩 방식을 가리키는 개념이라고 한다. 그러니까 이미지, ZIP 파일, 실행 파일 등을 모든 컴퓨터가 읽을 수 있도록 변환하는 과정이다.
인코딩된 문자열은 알파벳 대소문자와 숫자, 그리고 "+", "/" 기호 64개로 이루어지며, "="는 끝을 알리는 코드로 쓴다.
문자 그대로 64진법(2^6)을 사용하며, 바이너리 데이터를 6bit 씩 나누고 해당하는 문자를 색인표에서 맞게 치환하는 과정을 거친다. 6bit로 자르는 과정에서 모든 문자열이 3개씩 예쁘게 떨어지면 좋겠지만, 아닌 경우도 왕왕 존재하기 마련이다. 그런 경우를 대비해 padding을 하는데 남는자리에 = 기호를 통해서 채워주는 개념이다.
왜 써야 하나?
Base64 인코딩을 하면 6bit당 2bit의 *Overhead가 발생해 전송해야 될 데이터의 크기가 약 1/3 늘어난다. 그런데도 사용하는 이유는 무엇일까?
바로 통신과정에서 바이너리 데이터의 손실을 막기 위해서다. 플랫폼 독립적으로 바이너리 데이터를 전송할 필요가 있을 때, ASCII로 인코딩하여 전송하면 여러 문제가 발생한다. ASCII는 7bit이기에 나머지 1bit를 처리하는 방식이 시스템 별로 상이하고, 일부 제어 문자의 경우 시스템 별로 다른 코드 값을 가지기 때문이다.
즉, Base64는 HTML 또는 Email과 같이 문자를 위한 Media에 Binary Data를 포함해야 될 필요가 있을 때, 포함된 Binary Data가 시스템 독립적으로 동일하게 전송 또는 저장되는 걸 보장하기 위해 사용한다.
-
*Overhead: 프로그램의 실행흐름 도중에 동떨어진 위치의 코드를 실행시켜야 할 때 , 추가적으로 시간, 메모리, 자원이 사용되는 현상
'Programming > ETC' 카테고리의 다른 글
[CORS] OPTIONS 메소드 (0) | 2022.11.01 |
---|---|
JWT란? (0) | 2022.11.01 |
[ESLint] 'Delete CR' 에러 해결 방법 (0) | 2022.09.11 |
[Spring] Spring Boot JPA hibernate DDL 인코딩 오류 (0) | 2022.07.27 |
Gradle이란? (0) | 2022.07.26 |
댓글