API

Open AI chat GPT Vision API 사용해보자

Solo.dev 2025. 1. 4. 22:26

오늘은 React Native Expo 환경에서 OpenAI의 Vision API를 사용하는 방법을 정리해보겠습니다. 기본적으로 Text Generation API를 사용해본 경험이 있어서 Vision API를 사용해보는 데 도전했습니다.

이 글에서는 이미지와 텍스트를 분석하는 기능을 구현하는 과정을 단계별로 설명합니다.

 

1. Vision API란?

OpenAI의 Vision API는 이미지 분석과 관련된 기능을 제공합니다. 텍스트와 이미지를 조합하여 더욱 정교한 분석을 수행할 수 있습니다. 공식 문서 링크는 여기를 참고하세요.

 

2. 환경 설정

React Native 프로젝트는 Expo 기반으로 설정하였으며, Node.js 환경에서 OpenAI와 Expo 라이브러리를 활용합니다.

주요 라이브러리:

  • openai: OpenAI API와 통신
  • axios: 이미지 URL을 Base64 형식으로 변환
  • expo-image-picker: 사용자가 로컬 이미지 파일을 선택

Vision doc link

https://platform.openai.com/docs/guides/vision

 

 

3. 코드 구성

3.1 Vision API 요청 (vision.ts)

OpenAI Vision API와 통신하기 위한 코드를 작성합니다. 이미지 URL을 직접 전달하거나 Base64로 변환하여 분석 요청을 보냅니다.

 

import OpenAI from "openai";   // OpenAI SDK 임포트
import axios from "axios";    // axios는 이미지 URL을 base64로 변환하는 데 사용됩니다

// OpenAI 클라이언트 설정
const openai = new OpenAI({
  apiKey: 'sk-aBZrn2L_j_E85kjuZNYOyAxr0EUfc16p4v2MZZa5iKT3BlbkFJzlThgWwrJELGTKQOh-9mmDB5hMBReLJGzVjTSuf3cA', // API 키 입력
});


// 이미지 URL과 텍스트를 사용한 요청
export const analyzeImage = async (imageUrl: string, inputText: string) => {
  try {
    // OpenAI API 호출
    const response = await openai.chat.completions.create({
      model: "gpt-4o-mini",  // 사용하려는 모델
      messages: [
        {
          role: "user",
          content: inputText, // 텍스트 메시지
        },
        {
          role: "user",
          content: [
            {
              type: "text",
              text: inputText,
            },
            {
              type: "image_url",  // 이미지 URL을 사용하도록 설정
              image_url: { url: testimageurl }, // 이미지 URL
            },
          ],
        },
      ],
    });

    return response.choices[0].message.content; // 분석된 결과 반환
  } catch (error) {
    console.error("Error analyzing image:", error);
    return "이미지 분석 실패"; // 오류 메시지 반환
  }
};

 

{하지만 실제로 로컬 이미지를 분석하려면 base 64로 변환해야 하기 때문에 비용이 비싸집니다.}

 

 

2. ImagePicker.ts

import * as ImagePicker from 'expo-image-picker';

// 이미지 선택 함수
export const pickImage = async (): Promise<string | null> => {
  const { status } = await ImagePicker.requestMediaLibraryPermissionsAsync();
  if (status !== 'granted') {
    alert('이미지 접근 권한이 필요합니다.');
    return null;
  }

  const result = await ImagePicker.launchImageLibraryAsync({
    mediaTypes: ImagePicker.MediaTypeOptions.Images,
    allowsEditing: true,
    aspect: [4, 3],
    quality: 1,
  });

  // 선택이 취소된 경우
  if (result.canceled) {
    return null;  // 사용자가 이미지를 선택하지 않았을 때
  }

  // result.assets 배열에서 첫 번째 이미지 선택
  if (result.assets && result.assets.length > 0) {
    return result.assets[0].uri || null;  // 첫 번째 이미지 URI 반환
  }

  return null;  // 'uri'가 존재하지 않으면 null 반환
};

 

 

사용자가 로컬 기기에서 이미지를 선택할 수 있도록 Expo의 ImagePicker 라이브러리를 사용합니다.

 

3.index.ts ( 어플 진입점)

 
import React, { useState } from "react";
import { Image, StyleSheet, Text, View, ScrollView } from "react-native";
import ParallaxScrollView from "@/components/ParallaxScrollView";
import { Box } from "@/components/ui/box";
import { Textarea, TextareaInput } from "@/components/ui/textarea";
import { Button, ButtonText } from "@/components/ui/button";
import { pickImage } from "@/src/service/ImagePicker"; // 이미지 선택 함수 import
import { analyzeImage } from "@/src/api/vision"; // 이미지 분석 함수 import

export default function HomeScreen() {
  const [inputText, setInputText] = useState(""); // 입력 텍스트 상태
  const [imageUri, setImageUri] = useState<string | null>(null); // 선택된 이미지 URI 상태
  const [responseText, setResponseText] = useState<string | null>(null); // 분석 결과 상태

  // 이미지 선택 함수
  const handleImageSelect = async () => {
    const selectedImageUri = await pickImage(); // pickImage 호출
    if (selectedImageUri) {
      setImageUri(selectedImageUri); // 선택된 이미지 URI 상태에 저장
    }
  };

  // 텍스트 입력 및 이미지 분석 요청 함수
  const handleAnalyze = async () => {
    if (!imageUri || !inputText) {
      setResponseText("이미지와 텍스트를 모두 입력해야 합니다.");
      return;
    }

    // 텍스트와 이미지를 분석 함수에 전달
    const result = await analyzeImage(imageUri, inputText);
    setResponseText(result); // 분석된 결과 상태에 저장
  };

  return (
    <ParallaxScrollView
      headerBackgroundColor={{ light: "#A1CEDC", dark: "#1D3D47" }}
      headerImage={
        <Image
          source={imageUri ? { uri: imageUri } : require("@/assets/images/partial-react-logo.png")}
          style={styles.reactLogo}
        />
      }
    >
      {/* 응답 결과를 표시할 박스 컴포넌트 */}
    <Box className="bg-gray-200 p-4 rounded-lg flex-1 mt-4">
      <ScrollView contentContainerStyle={{ paddingBottom: 20 }}>
        {responseText && <Text style={styles.responseAnswer}>{responseText}</Text>}
      </ScrollView>
    </Box>

      {/* 텍스트 입력 필드 */}
      <Textarea
        size="xl"
        isReadOnly={false}
        isInvalid={false}
        isDisabled={false}
        className="w-128 mt-4"
      >
        <TextareaInput
          placeholder="질문을 입력하세요..."
          value={inputText}
          onChangeText={(text) => setInputText(text)} // 입력 값 업데이트
        />
      </Textarea>

      {/* 버튼 컴포넌트 - 이미지 선택 버튼 */}
      <Button size="md" variant="outline" action="primary" onPress={handleImageSelect}>
        <ButtonText>사진 선택</ButtonText>
      </Button>

      {/* 분석 버튼 */}
      <Button size="md" variant="outline" action="primary" onPress={handleAnalyze}>
        <ButtonText>분석하기</ButtonText>
      </Button>
    </ParallaxScrollView>
  );
}

const styles = StyleSheet.create({
  reactLogo: {
    height: 178,
    width: 290,
    bottom: 0,
    left: 0,
    position: "absolute",
  },
  responseAnswer: {
    fontSize: 16,
    fontWeight: "bold",
    color: "#333",
  },
  responseSource: {
    fontSize: 14,
    color: "#666",
  },
  selectedImage: {
    width: 200,
    height: 200,
    marginBottom: 20,
    borderRadius: 10,
    alignSelf: "center",
  },
});

 

 

간단히 구성된 홈스크린입니다. 여기서 원래 동작이라면 User 가 사진선택 누르면 

 

 

이렇게 선택 하고 

 

 

이렇게 뜨는게 동작 의도인대요 원래는 다 채워야되는데 일단 놔둘게요

 

그 다음 준비한 테스트 이미지 링크를 넣고 요구사항을 넣어줍니다.

제가 준비한 이미지는 이건데요 이 사진 링크를 Vision.ts 에 넣어주고 

 

위에 텍스트 추출 해달라고 해봤습니다.

 

 

보이시나요 텍스트 꽤 잘하죠? 제가 많은 OCR 사용해본 결과 한국어 이정도 정확성 가진 OCR이 많이 없는데

간단하고 쉽게 API 사용하면서 구성이 되네요 여기서 추가로 추출된 텍스트로도 여러가지 응용이 가능하니 

확실히 GPT APi 편하긴 합니다 . 하지만 큰 단점이 있는데요. 이렇게 이미지 보낼 때는 비용이 비쌉니다 

 

 

보이시나요 110,574 token  3번인가 4번 요청 보냈는데 토큰이 이렇게 빠져나갑니다 .

 

가격이 너무 비싸요 

GPT-4o 비전 모델:

  • 가격: 이미지 처리에 대해 1K 입력 토큰당 0.00125달러입니다.
    • 이 경우, 이미지 입력을 토큰 단위로 처리합니다.
    • 즉, 이미지 크기와 세부 설정에 따라 입력 토큰의 수가 달라지며, 가격은 토큰 수에 비례해서 발생합니다.

GPT-4o mini 모델:

  • 가격: 이미지 처리에 대해서는 이미지 한 장당 0.001275달러입니다.
    • 이 모델은 이미지 처리에 대해 단일 요금을 부과합니다. 즉, 이미지의 크기나 토큰 수와 관계없이 매번 동일한 요금이 부과됩니다.

 

mini 로 해도 100번만 돌려도 0.1275 달러네요 

mini 안하면 방금 11만 토큰이니까  0.1375 달러 사용한거네요