cv2 imdecode, imencode и передача изображений по протоколу http с использованием составного кодера

#python #numpy #opencv #flask #bjoern

Вопрос:

Я в тупике, может быть, я что-то неправильно понимаю, поэтому я опубликую и посмотрю, какие идеи вернутся.

У меня есть приложение API Flask (bjoern WSGI), которое использует JWT для аутентификации. Я отправляю приложению flask некоторые учетные данные и локальный URL-адрес для доступа к API, который будет предоставлять изображения. Затем приложение flask запускает несколько МЛ на изображениях и отвечает данными обнаружения. Как только клиент получает ответ от приложения flask, он также запрашивает соответствующее изображение из API (двойное попадание).

Что я хочу сделать, так это передать соответствующее изображение из приложения flask обратно клиенту вместе с данными обнаружения в формате JSON. Я достиг всего этого с помощью requests-toolbox и составного кодера. Моя проблема заключается в том, что я пытаюсь использовать cv2.imdecode для кодирования байтов response.content в формате jpeg на стороне клиента. Вот соответствующий код, и я также опубликую вывод и ошибку. Я не хочу записывать изображение на диск и считывать его обратно в массив numpy, я пытаюсь сделать все это в памяти.

Это на стороне клиента, в этом разделе он отправляет запрос в приложение flask.

 do_raise = True  try:  from requests_toolbelt.multipart import decoder  r = requests.post(  url=ml_object_url,  headers=auth_header,  params=params,  # json doesnt send when sending files to mlapi, so we send the file and the json together  json=mlapi_json if not files else None,  files=files,  )  r.raise_for_status()  except ValueError as v_ex:  # pass as we will do a retry loop? -gt; urllib3 has a retry loop built in but idk if that works here  if v_ex == "BAD_IMAGE":  pass  except requests.exceptions.HTTPError as http_ex:  if http_ex.response.status_code == 400:  if args.get('file'):  g.logger.error(  f"There seems to be an error trying to send an image from zm_detect to mlapi, looking into it"  )  else:  g.logger.error(f"{http_ex.response.json()}")  elif http_ex.response.status_code == 500:  g.logger.error(f"There seems to be an Internal Error with the mlapi host, check mlapi logs!")  else:  g.logger.error(f"ERR CODE={http_ex.response.status_code} {http_ex.response.content=}")  except urllib3.exceptions.NewConnectionError as urllib3_ex:  g.logger.debug(f"{lp} {urllib3_ex.args=} {urllib3_ex.pool=}")  g.logger.error(  f"There seems to be an error while trying to start a new connection to the mlapi host -gt; {urllib3_ex}")  except requests.exceptions.ConnectionError as req_conn_ex:  g.logger.error(  f"There seems to be an error while trying to start a new connection to the mlapi host -gt; "  f"{req_conn_ex.response}"  )  except Exception as all_ex:  g.logger.error(  f"{lp} error during post to mlapi host-gt; {all_ex}"  )  # g.logger.debug(f"traceback-gt; {format_exc()}")  else:  do_raise = False  data: Optional[dict] = None  multipart_data: decoder.MultipartDecoder = decoder.MultipartDecoder.from_response(r)  part: decoder.MultipartDecoder.from_response  img: Optional[bytes] = None  for part in multipart_data.parts:  # part = part.headers.decode('utf-8')  if part.headers.get(b'Content-Type') == b'image/jpeg':  print(f' got an image')  img = part.content  print(f"{type(img)}")  print(f"{len(img)=}")  elif part.headers.get(b'Content-Type') == b'application/json':  print(f"got json data")  data = part.content.decode('utf-8')  print(data)  np_img = np.asarray(bytearray(img), dtype=np.uint8)  print(f"img after np,asarray(bytearray()) -gt; {type(np_img) = }")  print(f"{len(np_img)=}")   try:  new_img = cv2.imdecode(img, cv2.IMREAD_UNCHANGED)  except Exception as exc:  print(f"EXCEPTION while cv2.imdecode")  print(exc)  else:  print(f"img after cv2.imdecode -gt; {type(new_img) = }")  if new_img is not None:  if options.get("resize", 'no') != "no":  new_img = resize_image(img, options.get("resize"))  data["matched_data"]["image"] = new_img  else:  print(f"exiting due to image error")  g.logger.log_close(exit=1)  g.logger.log_close(exit=1)  exit(1)  return data  finally:  if do_raise:  raise ValueError('MLAPI remote detection error!')  

Вот часть кода в приложении flask, который обрабатывает захват изображения из API и кодирует его для передачи в конвейеры модели ML. Этот код работает так, как и ожидалось.

 r = response  img = np.asarray(bytearray(response.content), dtype="uint8")  img = cv2.imdecode(img, cv2.IMREAD_COLOR) # RGB ?  self.orig_h_w = img.shape[:2]  return img  

So now there is a variable that contains img that is decoded into a jpg by cv2.imdecode. This image format (numpy.ndarray) can then be passed to the opencv DNN modules or be passed off to the pycoral models for TPU inference. This is the image I want to send back to the client. Here is the code I am using to accomplish that.

 img = matched_data['image'].tobytes()  # Remove the numpy.ndarray formatted image from matched_data because it is not JSON serializable  matched_data['image'] = None  # Construct a multipart response that contains the detection data and the image  success = False  if matched_data["frame_id"]:  success = True  resp_json = {  'success': success,  'matched_data': matched_data,  'all_matches': all_matches,  }  from requests_toolbelt import MultipartEncoder  multipart_encoded_data = MultipartEncoder(  fields={  'json': (None, json.dumps(resp_json), 'application/json'),  'image': (f"event-{g.eid}-frame-{matched_data['frame_id']}.jpg", img, 'image/jpeg')  }  )  response = Response(multipart_encoded_data.to_string(), mimetype=multipart_encoded_data.content_type)  if success:  g.logger.info(  f"{lp} returning matched detection -gt; {matched_data}",  )  g.logger.debug(  f"{lp} returning all detections -gt; {all_matches}")  else:  g.logger.info(  f"{lp} no detections to return"  )  return response  

Now on the client side separate the JSON and image and convert the image into a usable format -gt;

 do_raise = False  data: Optional[dict] = None  multipart_data: decoder.MultipartDecoder = decoder.MultipartDecoder.from_response(r)  part: decoder.MultipartDecoder.from_response  img: Optional[bytes] = None  for part in multipart_data.parts:  # part = part.headers.decode('utf-8')  if part.headers.get(b'Content-Type') == b'image/jpeg':  print(f' got an image')  img = part.content  print(f"{type(img)}")  print(f"{len(img)=}")  elif part.headers.get(b'Content-Type') == b'application/json':  print(f"got json data")  data = part.content.decode('utf-8')  print(data)  np_img = np.asarray(bytearray(img), dtype=np.uint8)  print(f"img after np,asarray(bytearray()) -gt; {type(np_img) = }")  print(f"{len(np_img)=}")   try:  new_img = cv2.imdecode(img, cv2.IMREAD_UNCHANGED)  except Exception as exc:  print(f"EXCEPTION while cv2.imdecode")  print(exc)  else:  print(f"img after cv2.imdecode -gt; {type(new_img) = }")  if new_img is not None:  if options.get("resize", 'no') != "no":  new_img = resize_image(img, options.get("resize"))  data["matched_data"]["image"] = new_img  else:  print(f"exiting due to image error")  g.logger.log_close(exit=1)  g.logger.log_close(exit=1)  exit(1)  return data   

Ошибка, которую я получаю, молчит, она просто возвращает «Нет» — gt;

 # Grabbing image using an http request and converting into a jpeg  11/07/21 20:44:30.623202 zm_mlapi[37535] DBG1 Media:659 ['std.out' --gt; image from ZM API as response.content - type(img) = lt;class 'bytes'gt; - len(img) = 205125]  11/07/21 20:44:30.627857 zm_mlapi[37535] DBG1 Media:661 ['std.out' --gt; after np.asarray(bytearray(img), np.uint8) - type(img) = lt;class 'numpy.ndarray'gt; - len(img) = 205125]  11/07/21 20:44:30.658582 zm_mlapi[37535] DBG1 Media:663 ['std.out' --gt; after cv2.imdecode(img, cv2.IMREAD_COLOR) - type(img) = lt;class 'numpy.ndarray'gt; - len(img) = 1080]  11/07/21 20:44:30.67595 zm_mlapi[37535] DBG2 pyzm_utils:386 [resize:img: success using resize=800.0 - original dimensions: 1920*1080 - resized dimensions: 450*800]  11/07/21 20:44:30.678568 zm_mlapi[37535] DBG1 Media:681 ['std.out' --gt; after resize - type(img) = lt;class 'numpy.ndarray'gt; - len(img) = 450]  # returned image to the class that requested it (ML Pipeline)  11/07/21 20:44:30.687835 zm_mlapi[37535] DBG1 detect_sequence:1048 ['std.out' --gt; DETECT STREAM: FRAME RETURNED FROM MEDIA CLASS --gt; type(frame) = lt;class 'numpy.ndarray'gt; - len(frame) = 450]  11/07/21 20:44:33.582062 zm_mlapi[37535] DBG1 detect_sequence:1656 ['std.out' --gt; before returning matched data - type(matched_data['image']) = lt;class 'numpy.ndarray'gt; - len(matched_data['image']) = 450]    # Return image to the flask app, now the flask app has to construct a response with JSON and the image  11/07/21 20:44:33.588139 zm_mlapi[37535] DBG1 mlapi:587 ['std.out' --gt; type(matched_data['image']) = lt;class 'numpy.ndarray'gt; - len(matched_data['image']) = 450]  11/07/21 20:44:33.591981 zm_mlapi[37535] DBG1 mlapi:590 ['std.out' --gt; before converting using .tobytes() - type(img) = lt;class 'numpy.ndarray'gt; - len(img) = 450]  11/07/21 20:44:33.596642 zm_mlapi[37535] DBG1 mlapi:594 ['std.out' --gt; after converting using .tobytes() - type(img) = lt;class 'bytes'gt; - len(img) = 1080000]  11/07/21 20:44:33.611218 zm_mlapi[37535] DBG1 mlapi:611 ['std.out' --gt; multipart MIME TYPE -gt; multipart/form-data; boundary=e7f7b825a51d4184ad7f12e7bbc6f411]    # flask app returns the response to the client  11/07/21 21:00:58.393864 zmesdetect_m4[102768] DBG1 zm_detect:418 ['std.out' --gt; got json data]  11/07/21 21:00:58.395459 zmesdetect_m4[102768] DBG1 zm_detect:414 ['std.out' --gt; got an image with Content-Type - b'application/octet']  11/07/21 21:00:58.396815 zmesdetect_m4[102768] DBG1 zm_detect:422 ['std.out' --gt; success = True]  11/07/21 21:00:58.398169 zmesdetect_m4[102768] DBG1 zm_detect:423 ['std.out' --gt; img - type(img) = lt;class 'bytes'gt; - len(img) = 1080000]  11/07/21 21:00:58.39958 zmesdetect_m4[102768] DBG1 zm_detect:424 ['std.out' --gt; img[:50] = b'\gu\gu\gu]hv^iw_jx`kyalzgrx80kvx84itx82itx82itx82itx82itx82itx82ju']  11/07/21 21:00:58.401012 zmesdetect_m4[102768] DBG1 zm_detect:426 ['std.out' --gt; img after np.frombuffer(img, dtype=np.uint8) -gt; type(np_img) = lt;class 'numpy.ndarray'gt;]  11/07/21 21:00:58.402911 zmesdetect_m4[102768] DBG1 zm_detect:430 ['std.out' --gt; img after np_img.copy() -gt; type(np_img) = lt;class 'numpy.ndarray'gt;]  11/07/21 21:00:58.404296 zmesdetect_m4[102768] DBG1 zm_detect:432 ['std.out' --gt; len(np_img)=1080000]  11/07/21 21:00:58.405619 zmesdetect_m4[102768] DBG1 zm_detect:433 ['std.out' --gt; attempting to decode numpy array into a jpeg]  11/07/21 21:00:58.407144 zmesdetect_m4[102768] DBG1 zm_detect:442 ['std.out' --gt; img after cv2.imdecode -gt; type(new_img) = lt;class 'NoneType'gt;]  11/07/21 21:00:58.408474 zmesdetect_m4[102768] DBG1 zm_detect:448 ['std.out' --gt; exiting due to image error]   

Любое понимание будет оценено по достоинству! Я новичок в этом, так что, надеюсь, это правильный вопрос.

Комментарии:

1. Я также попытался использовать np.frombuffer(img, np.uint8) вместо np.asarray(bytearray(img), np.uint8). Оба вернули одинаковую длину и класс, я не уверен, что один лучше другого, но оба не работают для меня.

Ответ №1:

По словам пользователя Reddit @ES-Alexander, я должен был cv2.imencode(‘.jpg’, img), прежде чем конвертировать его с помощью .tobytes(). Это на стороне приложения flask, создающего составной закодированный ответ.

 img = matched_data['image']  assert isinstance(img, np.ndarray)  succ, img = cv2.imencode('.jpg', img)  # Remove the numpy.ndarray formatted image from matched_data   # because it is not JSON serializable  matched_data['image'] = None   img = img.tobytes()  

На стороне клиента -gt;

 # img = response.content basically img = np.frombuffer(img, dtype=np.uint8) img = cv2.imdecode(img, cv2.IMREAD_UNCHANGED)  

Журналы -gt;

 11/08/21 00:34:00.641026 zmesdetect_m4[124600] DBG1 zm_detect:418 ['std.out' --gt; got json data]  11/08/21 00:34:00.64252 zmesdetect_m4[124600] DBG1 zm_detect:414 ['std.out' --gt; got an image with Content-Type - b'application/octet']  11/08/21 00:34:00.643865 zmesdetect_m4[124600] DBG1 zm_detect:422 ['std.out' --gt; img - type(img) = lt;class 'bytes'gt; - len(img) = 187057]  11/08/21 00:34:00.645188 zmesdetect_m4[124600] DBG1 zm_detect:423 ['std.out' --gt; img[:50] = b'xffxd8xffxe0x00x10JFIFx00x01x01x00x00x01x00x01x00x00xffxdbx00Cx00x02x01x01x01x01x01x02x01x01x01x02x02x02x02x0  2x04x03x02x02x02x02x05x04x04x03']  11/08/21 00:34:00.646544 zmesdetect_m4[124600] DBG1 zm_detect:428 ['std.out' --gt; img after np.asarray(img, dtype=np.uint8) -gt; type(np_img) = lt;class 'numpy.ndarray'gt;]  11/08/21 00:34:00.647876 zmesdetect_m4[124600] DBG1 zm_detect:434 ['std.out' --gt; len(np_img)=187057]  11/08/21 00:34:00.649185 zmesdetect_m4[124600] DBG1 zm_detect:435 ['std.out' --gt; attempting to decode numpy array into a jpeg]  11/08/21 00:34:00.657759 zmesdetect_m4[124600] DBG1 zm_detect:444 ['std.out' --gt; img after cv2.imdecode -gt; type(new_img) = lt;class 'numpy.ndarray'gt;]  11/08/21 00:34:00.659152 zmesdetect_m4[124600] DBG1 zm_detect:447 ['std.out' --gt; image_shape = (450, 800, 3)]