Framework/Flask

[Flask] numpy array to json 변환 후 전송

깜태 2021. 5. 12. 13:42
728x90

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

728x90