파이썬으로 몽고DB에 이미지 저장하고 불러오기(base64인코딩)

Thomas·2023년 5월 10일
2

Base 64

8비트 이진 데이터를 문자 코드에 영향받지 않는 ASCII 문자열로 바꾸어 인코딩하는 방식

https://www.base64-image.de/

예시) 맥도날드사진을 base 64으로

위에 'For use in < img > elements'처럼 인코딩하면 이미지를 텍스트로 표현 할 수 있다.

Base64 장점

1. 구현이 쉽다

DB에 이미지를 넣는 방식은 서버에 이미지를 직접 넣지 않는 장점이 있다.
인코딩한 텍스트를 DB에 넣고 쓸 땐 불러와 화면에 < img> 형식으로 뿌리면 매우 간단한 구현이 가능하다.

2. 이미지 렌더링

브라우저가 이미지 랜더링 시, base64로 표현할 경우 문서로딩과 같이 로딩되기에 끊기지 않고 불려온다.

Base64 단점

1. 용량이 커진다

256가지를 표현할 수 있는 바이트를 64가지를 사용해서 표현하기 떄문이다.
다시 말해, 8비트를 6비트로 표현하는 것이다.
따라서 Base64 인코딩을 사용하면 원본보다 대략 33%의 크기 증가가 발생한다.

2. 가독성이 떨어진다

사진의 크기가 크다면, 태그에 매우긴 텍스트가 보이기떄문에 가동성에 어려움을 준다

Flask 이미지 업로드 방식

  1. static에 집어넣기
  2. gridfs 사용 -> 우리가 사용할 방법!
  3. DB에 URL만 저장하며 img 서버를 따로 만들어 두어 DB에서 받아온 URL을 통해 접속

구현

나는 2번 방법을 사용했다
1번은 https://taehyeki.tistory.com/153 에서 한 번 보시고 시도 해보세요
통상 적으로 3번을 많이 쓰지만...
내가 하는 프로젝트는 일주일만 하는 미니프로젝트라 과감하게 PASS...

app.py

from flask import Flask, render_template, request, jsonify
app = Flask(__name__)

from pymongo import MongoClient
import certifi
ca=certifi.where()

client = MongoClient('MongoDB Altas URL주소 넣으세요', tlsCAFile=ca)
db = client.<DB테이블명>

####### 1 #######
import base64
import gridfs
fs = gridfs.GridFS(db)

# 메인 페이지
@app.route('/')
def home():
   return render_template('index.html')


# Post 요청 API: 식당 저장
@app.route('/restaurant', methods=["POST"])
def restaurant_post():
    placeName_receive = request.form['placeName_give']
    address_receive = request.form['address_give']
    ####### 2 #######
    picture_receive = request.files['picture_give']
    star_receive = request.form['star_give']
    review_receive = request.form['review_give']
    #print(placeName_receive,address_receive,picture_receive,star_receive,review_receive)
	
    ####### 3 #######
    # GridFs를 통해 파일을 분할하여 DB에 저장
    file_img_id = fs.put(picture_receive)
     
     # DB에 저장 
    doc = {
        'name': placeName_receive,
        'address': address_receive,
        'picture': file_img_id,
        'star': star_receive,
        'review': review_receive
        }
    db.<DB테이블명>.insert_one(doc)

    return  jsonify({'result': 'success', 'msg': '저장완료'})
    
GET 요청 API
@app.route("/restaurant", methods=["GET"])
def restaurant_get():
    # 데이터 모두 가져오기
    all_restaurants = list(db.<DB테이블명>.find({}, {'_id': False}))
    # 이미지 데이터 검색 및 인코딩
    for restaurant in all_restaurants:
        picture_id = restaurant['picture']  # ObjectId
        if picture_id:
            try:
                # ObjectId를 사용하여 이미지 데이터 검색
                image_data = fs.get(picture_id).read()
                # 이미지 데이터를 base64로 인코딩 및 디코딩
                base64_img = base64.b64encode(image_data).decode('utf-8')
                # 'picture' 필드 업데이트
                restaurant['picture'] = base64_img
            except gridfs.errors.NoFile as e:
                # 이미지 데이터를 가져오지 못한 경우 예외처리
                print("요구하신 파일이 존재하지 않습니다")

    return jsonify({'result': all_restaurants})

주석으로 이해 했을거라 믿습니다....

농담이고요.. 중요한 코드만 따로 설명 하겠습니다

DB에 저장하기

####### 1 #######
import base64
import gridfs
fs = gridfs.GridFS(db)
-> db와 연결시킨 fs를 만들어준다

####### 2 #######
picture_receive = request.files['picture_give']

form이 아닌 files를 받아야 하는게 핵심이다
그럴러면 Front-end쪽도 손을 봐줘야 한다

잠시만 Front-end 부분으로 가겠습니다!!!


index.html

프론트엔드 부분에서 FormData을 이용하여 백엔드를 소통할 떄 사진 부분을 input 변수를 formData에 저장 하는 법

let input = document.querySelector('#FileName');
formData.append("picture_give", input.files[0]);

일반적이게 받는 법이랑 비교를 해볼까요?
let address = $('#addrName').val();
formData.append("address_give", address);

많이 다릅니다... 꼭 꼭 유의하세요...
저 삽집 많이 했습니다... ㅜㅜ


다시 Back-end 부분으로 돌아 옵시다!!
app.py으로 돌아가세요!!

####### 3 #######
file_img_id = fs.put(picture_receive)
GridFs를 통해 파일을 분할하여 DB에 저장하는데
그러면 아래의 두개의 폴더가 생성이 된다

fs.chunks

맨위에 사이트에서 맥도날드사진을 base 64으로 한 사진에 'For use as CSS background'에 있는 값인 4AAQS...이랑 같은 값이라는 걸 알수 있다.

fs.files

DB


위에 3개의 파일을 보면 ObjectId가 일치한다. 그래서 모두 연결되어있다고 봐도 무방하다.

DB에서 불러오기

DB에 넣는건 아주 쉬운데 불러오는건 살짝 난이도가 올라간다.

all_restaurants = list(db.<DB테이블명>.find({}, {'_id': False}))

    for restaurant in all_restaurants:
        picture_id = restaurant['picture']  # ObjectId
        if picture_id:
            try:
                image_data = fs.get(picture_id).read()
                base64_img = base64.b64encode(image_data).decode('utf-8')
                restaurant['picture'] = base64_img
            except gridfs.errors.NoFile as e:
                print("요구하신 파일이 존재하지 않습니다")

    return jsonify({'result': all_restaurants})

일단 저는 Properties중에서 사진부분만 불러오면 되는 상황입니다.
all_restaurants에 디비에 있는 모든걸 불러옵니다.
그런 다음 for loop으로 돌립니다.
restaurant['picture']는 ObjectId가 담아 있어서 picture_id에 담아주고요
만약 존재 한다고 손상된 이미지 파일이 아니면
fs.get()을 통해 바이너리 데이터를 얻습니다.
만약 손상된 이미지 파일이라면 gridfs.errors.NoFile으로 예외처리가 됩니다.
바이너리 데이터를 얻었으면 인코딩 디코딩 과정을 걸치고
ObjectId값이 담아 있던 restaurant['picture']에 디코딩된 값을 넣어줍니다.
마지막으로 프론트엔드쪽으로 jsonify로 넘겨주면 끝!!

고의적으로 확장자를 image 확장자로 바꾼건 예외처리가 안됩니다.

용량크기의 단점 해결법

  1. 압축: 데이터를 압축하여 크기를 줄일 수 있습니다. 압축 알고리즘을 사용하여 데이터를 압축한 후에 base64 인코딩을 적용할 수 있습니다. 이를 통해 데이터 크기를 상당히 줄일 수 있습니다. 압축된 데이터를 전송 또는 저장 후에는 필요에 따라 압축을 해제해야 합니다.
  2. 이미지 해상도 조정: 이미지 데이터의 크기를 줄이는 방법 중 하나는 해상도를 조정하는 것입니다. 이미지를 원본보다 작은 해상도로 변환하여 저장하거나 전송할 수 있습니다. 이는 이미지의 세부 정보를 일부 손실하게 될 수 있지만, 데이터 크기를 크게 줄일 수 있습니다.
  3. 데이터 청크 처리: 대용량 데이터의 경우, 데이터를 작은 청크로 분할하여 전송하거나 저장할 수 있습니다. 청크 단위로 데이터를 나누어 전송하고, 받은 쪽에서 청크를 조합하여 원본 데이터를 복원할 수 있습니다. 이를 통해 데이터 전송 중에 발생하는 문제를 최소화할 수 있습니다.
  4. 파일 형식 선택: 데이터를 저장할 때 파일 형식을 고려할 수 있습니다. 일부 파일 형식은 기본적으로 압축되어 저장되거나 이미지 데이터를 압축하는 알고리즘을 내장하고 있을 수 있습니다. 적절한 파일 형식을 선택하여 데이터 크기를 줄일 수 있습니다.

    데이터 크기를 제한하는 것은 상황과 요구사항에 따라 다를 수 있으며, 데이터의 중요성, 대역폭 제한, 전송 속도 등을 고려해야 합니다. 특히 네트워크 통신이나 저장 공간이 제한된 환경에서는 데이터 크기를 최소화하는 방법을 고려해야 합니다.

또 다른 해결법

저는 사진을 사용할 목적은 단순 웹사이트에 식당 사진을 첨부 하는거라서 고해상도 이미지 파일은 필요가 없습니다.

그래서 적당한 이미지 사이즈 범위를 설정할 수 있습니다. 이는 일반적인 웹 환경에서 사용하기 위한 추천 범위이며, 상황에 따라 조정될 수 있습니다.

  1. 파일 크기: 이미지의 파일 크기는 웹에서의 로딩 속도를 고려하여 적절히 제한되어야 합니다. 일반적으로 몇 십 킬로바이트(KB)에서 몇 백 킬로바이트(KB) 사이의 파일 크기를 유지하는 것이 적절합니다. 이미지의 품질과 세부 정보가 중요하지 않은 경우에는 압축률을 높여 파일 크기를 줄일 수 있습니다.
  2. 파일 포맷: 웹에서 사용되는 주요 이미지 포맷은 JPEG와 PNG입니다. JPEG는 사진 및 복잡한 그래픽에 적합하며, 파일 크기를 상대적으로 작게 유지할 수 있습니다. PNG는 투명도가 필요한 그래픽이나 아이콘에 적합하며, 비교적 더 큰 파일 크기를 가질 수 있습니다.

따라서, 웹사이트에서 식당 사진을 첨부할 때는 다음과 같은 이미지 사이즈 범위를 고려할 수 있습니다:

  • 파일 크기: 몇 십 킬로바이트(KB)에서 몇 백 킬로바이트(KB) 사이
  • 파일 포맷: JPEG 또는 PNG (사진이 주로 사용되므로 JPEG를 선호)
profile
Backend Programmer

0개의 댓글

관련 채용 정보