TL;DR(Too Long, Didn't Read) - 3줄 요약
1. requests를 이용한 post 테스트 시, header를 잘 확인해야 한다
(이미지는 image/jpeg 인데, application/json 으로 보내면 보통 에러가 난다)
2. ndarray.tobytes()나 opencv를 이용해 로 bytes 형태로 보낼 수도 있고, 파일 통째로 보내는 것도 가능하다.
3. 굳이 application/json 형태로 보내고 싶다면 인코딩/디코딩을 잘 해야한다.
ps. application/json 형태로 보낸 것보다, bytes형태로 보낸 게 속도가 더 빠르다 (당연한 얘기인가?)
서론
저의 경우는 플라스크를 이용해 이미지를 보내려고 하는데 문제가 생겨서 검색해봤습니다.
처음 시도는 openCV를 이용해 이미지를 읽고, img.tobytes()를 이용해서 보내면 되겠지? 라고 생각해봤는데요,
그래서 시도해봤습니다.
img = cv2.imread(imgpath)
img_dict = {'img':img.tobytes()}
img_dict = json.dumps(img_dict)
headers = {'Content-Type': "application/json", 'charset':'utf-8'}
post('http://localhost:5000/send_data', data=img_dict, headers=headers)
그래서 결과는??
TypeError: Object of type bytes is not JSON serializable 에러가 발생해서 검색해봤습니다.
object 형태는 안되서 다른 형식으로 바꿔보라는 내용이였고, 다른 시도를 해본 건 decode('utf8') 형태로 변경하는 거였습니다.
그래서 변경해서 다시 시도해봤습니다.
img = cv2.imread(imgpath)
img_dict = {'img':img.tobytes().decode('utf-8'}
img_dict = json.dumps(img_dict)
headers = {'Content-Type': "application/json", 'charset':'utf-8'}
post('http://localhost:5000/send_data', data=img_dict, headers=headers)
그랬더니
UnicodeDecodeError: 'utf-8' codec can't decode byte 0x83 in position 1746: invalid start byte 에러가 발생했습니다.
하... 어떡하지 하고 하면서 이것저것 시도해보다가
제가 알아낸 방법은 2가지입니다
1. base64 인코딩
테스트용 코드
img = cv2.imread(imgpath)
img_str = base64.b64encode(cv2.imencode('.jpg', img)[1]).decode()
img_dict = {'img':img_str}
img_dict = json.dumps(img_dict)
headers = {'Content-Type': "application/json", 'charset':'utf-8'}
post('http://localhost:5000/send_data', data=img_dict, headers=headers)
이렇게 보내면 전송은 되고, 받는 쪽에서도 base64로 디코딩하고, np.frombuffer(), cv2.imdecode()를 이용하면 이미지가 행렬 형태로 온전히 돌아옵니다.
서버에서 받는 코드
data= request.get_json()
data = base64.b64decode(data)
jpg_arr = np.frombuffer(data, dtype=np.uint8)
img = cv2.imdecode(jpg_arr, cv2.IMREAD_COLOR)
# print(img.shape) 확인용
2. ndarray.tolist()
테스트용 코드
from requests import post
import cv2
import json
path = 'img.jpg'
img = cv2.imread(path)
img_dict = {'image': img.tolist()}
img_dict = json.dumps(img_dict)
headers = {'Content-Type': "application/json", 'charset':'utf-8'}
post('http://localhost:5000/send_data', data=img_dict, headers=headers)
열심히 검색해보다가, 알아보다가 header를 잘 설정해야 한다는 것을 배웠습니다
header를 jpeg로 잘 설정하면 아래와 같은 형식으로 쉽게 보낼 수 있습니다.
3. img.tobytes() | read(image_path, 'rb').read()
- 보낼 때 (post)
def get_img_bytes():
img_path = 'test_img.jpg'
img = cv2.imread(abs_path, cv2.IMREAD_COLOR)
img_bytes = cv2.imencode('.jpg', img)[1].tobytes()
# img_bytes = open(abs_path, 'rb').read() 속도 차이는 미미함
return img_bytes
headers = {'Content-Type': "image/jpeg"}
print(post(uri, data=get_img_bytes(), headers=headers).content)
- 받을 때 (server)
def post(self):
# if bytes stream
if request.data:
data = request.data
# if file format
elif request.files['file'] is not None:
data = request.files['file'].read()
jpg_arr = np.frombuffer(data, dtype=np.uint8)
img = cv2.imdecode(jpg_arr, cv2.IMREAD_COLOR)
print(img.shape)
결론
http 통신을 잘 몰라서 header 타입 하나 때문에 며칠 간의 삽질이 있었지만, 배움이 있어서 다행이였습니다.
[출처 및 참고] : stackoverflow.com/questions/26646362/numpy-array-is-not-json-serializable
pynative.com/python-serialize-numpy-ndarray-into-json/
stackoverflow.com/questions/29104107/upload-image-using-post-form-data-in-python-requests
'Framework > Flask' 카테고리의 다른 글
[flask] flask-restx로 간편하게 Swagger 지원하기 (0) | 2022.02.07 |
---|---|
[Flask] Flask rest API 라이브러리 비교 (0) | 2021.05.13 |
[Flask] code 400, message Bad request version (3) | 2021.05.11 |