본문 바로가기
Firmware Programming

[Firmware Programming] ESP32-CAM 보드 UDP camera frame 패킷 순서 제어 및 실시간 전송

by TYB 2024. 2. 10.
반응형

ESP32-CAM 연결 및 업로드, udp 통신방식은 각자 이해가 된 상태라고 가정하고 진행합니다.

 

참고자료가 있으면 찾아가면서 하려고 했으나, UDP를 통한 실시간 영상 전송 관련 알고리즘이나 코드를 거의 찾지 못해서 직접 짭니다.

 

https://program-developers-story.tistory.com/104

 

[Firmware Programming] ESP32-CAM 보드 usb로 upload 하기

필자는 아두이노 우노를 통해 업로드를 시도하였으나, 실패했다. 이틀 동안 해결방법을 찾아보았으나, 역시 실패하였다. 그래서 USB to gpio핀을 통해 direct로 연결해서 upload를 하였더니, 정상적으

program-developers-story.tistory.com

 

윗 글을 참고해서 ESP32-CAM보드에 write가 되고 wifi도 정상적으로 되는지 테스트가 완료되었다면

 

이제 자신이 해당 카메라를 사용하고 싶은대로 코딩을 하면 된다.

 

나는 ESP32-CAM 보드로 UDP 통신을 통해 카메라의 입력을 그대로 uint8_t 값의 data로 서버로 실시간 전송을 하고 싶었다.

하지만  MTU, 즉 실제 네트워크 환경에서 정한 하나의 패킷 전송 시 전송 가능한 최대 바이트 수가 1500바이트임에 반해, 전송해야 하는 이미지의 크기는 QVGA(320x240) 기준으로 4000바이트 정도 됬기 때문에 순서 제어 없이 막 전송하는 udp에 맞는 순서제어 알고리즘을 추가해줘야 한다.

 

알고리즘 없이 데이터를 전송하고 수신받는다면 어떤 패킷이 어떤 이미지의 어느 부분인지 알 수 없으므로, 당연히 정상적인 이미지가 될 수 없다.

 

고로, 나는 이미지 1개를 나타내는 8비트(= 1Byte = 0~255)사이의 값을 1번째 바이트에 넣어주고, 2번째 바이트에는 해당 이미지의 몇번 째 패킷인지를 넣어준다.
이미지를 나타내는 첫번째 바이트는 255까지 증가하면 다시 0부터 시작,  2번째 바이트는 하나의 이미지를 다 전송하면 다시 0부터 시작하도록 구현했다.

 

 

이 코드를 넣을 때는 EXAMPLE-> ESP32 -> CAMERA 예제를 열고 거기의 ino 파일 내용만 이걸로 변경해서 컴파일 및 업로드를 진행해주면 된다. 직접 스케치를 만들면 주변 라이브러리가 포함되지 않아 라이브러리 에러가 발생함.

#include <WiFi.h>
#include <WiFiUdp.h>
#include "esp_camera.h"

WiFiUDP udp;
const char *serverIp = "192.168.200.169";
const int serverPort = 12345;
//
// WARNING!!! PSRAM IC required for UXGA resolution and high JPEG quality
//            Ensure ESP32 Wrover Module or other board with PSRAM is selected
//            Partial images will be transmitted if image exceeds buffer size
//
//            You must select partition scheme from the board menu that has at least 3MB APP space.
//            Face Recognition is DISABLED for ESP32 and ESP32-S2, because it takes up from 15 
//            seconds to process single frame. Face Detection is ENABLED if PSRAM is enabled as well

// ===================
// Select camera model
// ===================
//#define CAMERA_MODEL_WROVER_KIT // Has PSRAM
//#define CAMERA_MODEL_ESP_EYE // Has PSRAM
//#define CAMERA_MODEL_ESP32S3_EYE // Has PSRAM
//#define CAMERA_MODEL_M5STACK_PSRAM // Has PSRAM
//#define CAMERA_MODEL_M5STACK_V2_PSRAM // M5Camera version B Has PSRAM
//#define CAMERA_MODEL_M5STACK_WIDE // Has PSRAM
//#define CAMERA_MODEL_M5STACK_ESP32CAM // No PSRAM
//#define CAMERA_MODEL_M5STACK_UNITCAM // No PSRAM
#define CAMERA_MODEL_AI_THINKER // Has PSRAM
//#define CAMERA_MODEL_TTGO_T_JOURNAL // No PSRAM
//#define CAMERA_MODEL_XIAO_ESP32S3 // Has PSRAM
// ** Espressif Internal Boards **
//#define CAMERA_MODEL_ESP32_CAM_BOARD
//#define CAMERA_MODEL_ESP32S2_CAM_BOARD
//#define CAMERA_MODEL_ESP32S3_CAM_LCD
//#define CAMERA_MODEL_DFRobot_FireBeetle2_ESP32S3 // Has PSRAM
//#define CAMERA_MODEL_DFRobot_Romeo_ESP32S3 // Has PSRAM
#include "camera_pins.h"

// ===========================
// Enter your WiFi credentials
// ===========================
const char* ssid = "HelloWirelessDCF7";//KT_GIGA_5G_Wave2_A201
const char* password = "1206013512";//1gghccxg225


//void startCameraServer();
//void setupLedFlash(int pin);

void setup() {
  Serial.begin(115200);
  Serial.setDebugOutput(true);
  Serial.println();

  camera_config_t config;
  config.ledc_channel = LEDC_CHANNEL_0;
  config.ledc_timer = LEDC_TIMER_0;
  config.pin_d0 = Y2_GPIO_NUM;
  config.pin_d1 = Y3_GPIO_NUM;
  config.pin_d2 = Y4_GPIO_NUM;
  config.pin_d3 = Y5_GPIO_NUM;
  config.pin_d4 = Y6_GPIO_NUM;
  config.pin_d5 = Y7_GPIO_NUM;
  config.pin_d6 = Y8_GPIO_NUM;
  config.pin_d7 = Y9_GPIO_NUM;
  config.pin_xclk = XCLK_GPIO_NUM;
  config.pin_pclk = PCLK_GPIO_NUM;
  config.pin_vsync = VSYNC_GPIO_NUM;
  config.pin_href = HREF_GPIO_NUM;
  config.pin_sccb_sda = SIOD_GPIO_NUM;
  config.pin_sccb_scl = SIOC_GPIO_NUM;
  config.pin_pwdn = PWDN_GPIO_NUM;
  config.pin_reset = RESET_GPIO_NUM;
  config.xclk_freq_hz = 20000000;
  config.frame_size = FRAMESIZE_UXGA;
  config.pixel_format = PIXFORMAT_JPEG; // for streaming
  //config.pixel_format = PIXFORMAT_RGB565; // for face detection/recognition
  config.grab_mode = CAMERA_GRAB_WHEN_EMPTY;
  config.fb_location = CAMERA_FB_IN_PSRAM;
  config.jpeg_quality = 12;
  config.fb_count = 1;
  
  // if PSRAM IC present, init with UXGA resolution and higher JPEG quality
  //                      for larger pre-allocated frame buffer.
  if(config.pixel_format == PIXFORMAT_JPEG){
    if(psramFound()){
      config.jpeg_quality = 10;
      config.fb_count = 2;
      config.grab_mode = CAMERA_GRAB_LATEST;
    } else {
      // Limit the frame size when PSRAM is not available
      config.frame_size = FRAMESIZE_SVGA;
      config.fb_location = CAMERA_FB_IN_DRAM;
    }
  } else {
    // Best option for face detection/recognition
    config.frame_size = FRAMESIZE_240X240;
#if CONFIG_IDF_TARGET_ESP32S3
    config.fb_count = 2;
#endif
  }

#if defined(CAMERA_MODEL_ESP_EYE)
  pinMode(13, INPUT_PULLUP);
  pinMode(14, INPUT_PULLUP);
#endif

  // camera init
  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) {
    Serial.printf("Camera init failed with error 0x%x", err);
    return;
  }

  sensor_t * s = esp_camera_sensor_get();
  // initial sensors are flipped vertically and colors are a bit saturated
  if (s->id.PID == OV3660_PID) {
    s->set_vflip(s, 1); // flip it back
    s->set_brightness(s, 1); // up the brightness just a bit
    s->set_saturation(s, -2); // lower the saturation
  }
  // drop down frame size for higher initial frame rate
  if(config.pixel_format == PIXFORMAT_JPEG){
    s->set_framesize(s, FRAMESIZE_QVGA);
  }

#if defined(CAMERA_MODEL_M5STACK_WIDE) || defined(CAMERA_MODEL_M5STACK_ESP32CAM)
  s->set_vflip(s, 1);
  s->set_hmirror(s, 1);
#endif

#if defined(CAMERA_MODEL_ESP32S3_EYE)
  s->set_vflip(s, 1);
#endif

// Setup LED FLash if LED pin is defined in camera_pins.h
#if defined(LED_GPIO_NUM)
  //setupLedFlash(LED_GPIO_NUM);
#endif

  WiFi.begin(ssid, password);
  WiFi.setSleep(false);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected");

  Serial.print("Camera Ready! IP is:");
  Serial.print(WiFi.localIP());
}
void sendImageWithSequence(uint8_t *imageData, size_t imageSize, uint8_t frameNo, uint8_t packetNo) {
  size_t remainingSize = imageSize;
  
  while (remainingSize > 0) {
    size_t chunkSize = std::min(remainingSize, (size_t)998);
    
    udp.beginPacket(serverIp, serverPort);
    udp.write(frameNo);  // 상위 바이트
    udp.write(packetNo);  // 하위 바이트
    udp.write(imageData, chunkSize);
    udp.endPacket();

    Serial.println(2 + chunkSize);

    imageData += chunkSize;
    remainingSize -= chunkSize;
    packetNo = (packetNo + 1) % 256;
  }
}

void loop() {
  static uint8_t frameNo = 0;
  camera_fb_t *fb = esp_camera_fb_get();

  uint8_t packetNo = 0;

  sendImageWithSequence(fb->buf, fb->len, frameNo, packetNo);

  esp_camera_fb_return(fb);
  Serial.println("");
  Serial.print("send image to Server Size: ");
  Serial.println(fb->len);
  frameNo = (frameNo + 1) % 256;
  delay(1000); // 1초 대기
}

 

 

이제 UDP로 보냈으니 받아주는 서버가 있어야겠죠?

 

파이썬으로 간단하게 이미지 받아서 저장하는 코드를 짜봅니다.

 

import socket
from io import BytesIO

UDP_IP = "0.0.0.0"  # ESP32의 IP 주소
UDP_PORT = 12345     # ESP32에서 설정한 포트 번호

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind((UDP_IP, UDP_PORT))

combined_data = b""  # 이미지 데이터를 합칠 변수

while True:
    data, addr = sock.recvfrom(1000)  # 데이터 수신 (최대 1000바이트)
    
    frame_no = data[0]    # 첫 번째 바이트는 frameNo
    packet_no = data[1]   # 두 번째 바이트는 packetNo
    image_data = data[2:]  # 나머지는 이미지 데이터

    print(f"Received Frame: {frame_no}, Packet: {packet_no}, Image Size: {len(image_data)} bytes")

    # 이미지 데이터를 합칩니다.
    combined_data += image_data

    # 패킷 크기가 1000보다 작으면서 frame_no 값이 같고, packet_no 보다 작은 패킷들을 합칩니다.
    if len(image_data) < 998 and packet_no > 0:
        # 합쳐진 이미지 데이터를 파일로 저장합니다.
        with open(f"received_image_frame_{frame_no}.jpg", "wb") as file:
            file.write(combined_data)

        # 이미지 처리가 끝나면 combined_data 초기화
        combined_data = b""

 

 

이렇게 하면 정상적으로 이미지 수신이 되는 걸 볼 수 있슴다.

 

 


 

 

윗 코드들은 이미지, 패킷 번호를 구분해주는 2바이트 포함해서 1000바이트를 보내는거고 MTU를 극한으로 활용하기 위해1450바이트로 변경했슴다. 어느 부분 변경했는지 확인해보시면 여러분도 직접 원하는 사이즈만큼 잘라서 보내실 수 있슴다!

 

#include <WiFi.h>
#include <WiFiUdp.h>
#include "esp_camera.h"

WiFiUDP udp;
const char *serverIp = "192.168.200.169";
const int serverPort = 12345;
//
// WARNING!!! PSRAM IC required for UXGA resolution and high JPEG quality
//            Ensure ESP32 Wrover Module or other board with PSRAM is selected
//            Partial images will be transmitted if image exceeds buffer size
//
//            You must select partition scheme from the board menu that has at least 3MB APP space.
//            Face Recognition is DISABLED for ESP32 and ESP32-S2, because it takes up from 15 
//            seconds to process single frame. Face Detection is ENABLED if PSRAM is enabled as well

// ===================
// Select camera model
// ===================
//#define CAMERA_MODEL_WROVER_KIT // Has PSRAM
//#define CAMERA_MODEL_ESP_EYE // Has PSRAM
//#define CAMERA_MODEL_ESP32S3_EYE // Has PSRAM
//#define CAMERA_MODEL_M5STACK_PSRAM // Has PSRAM
//#define CAMERA_MODEL_M5STACK_V2_PSRAM // M5Camera version B Has PSRAM
//#define CAMERA_MODEL_M5STACK_WIDE // Has PSRAM
//#define CAMERA_MODEL_M5STACK_ESP32CAM // No PSRAM
//#define CAMERA_MODEL_M5STACK_UNITCAM // No PSRAM
#define CAMERA_MODEL_AI_THINKER // Has PSRAM
//#define CAMERA_MODEL_TTGO_T_JOURNAL // No PSRAM
//#define CAMERA_MODEL_XIAO_ESP32S3 // Has PSRAM
// ** Espressif Internal Boards **
//#define CAMERA_MODEL_ESP32_CAM_BOARD
//#define CAMERA_MODEL_ESP32S2_CAM_BOARD
//#define CAMERA_MODEL_ESP32S3_CAM_LCD
//#define CAMERA_MODEL_DFRobot_FireBeetle2_ESP32S3 // Has PSRAM
//#define CAMERA_MODEL_DFRobot_Romeo_ESP32S3 // Has PSRAM
#include "camera_pins.h"

// ===========================
// Enter your WiFi credentials
// ===========================
const char* ssid = "HelloWirelessDCF7";//KT_GIGA_5G_Wave2_A201
const char* password = "1206013512";//1gghccxg225


//void startCameraServer();
//void setupLedFlash(int pin);

void setup() {
  Serial.begin(115200);
  Serial.setDebugOutput(true);
  Serial.println();

  camera_config_t config;
  config.ledc_channel = LEDC_CHANNEL_0;
  config.ledc_timer = LEDC_TIMER_0;
  config.pin_d0 = Y2_GPIO_NUM;
  config.pin_d1 = Y3_GPIO_NUM;
  config.pin_d2 = Y4_GPIO_NUM;
  config.pin_d3 = Y5_GPIO_NUM;
  config.pin_d4 = Y6_GPIO_NUM;
  config.pin_d5 = Y7_GPIO_NUM;
  config.pin_d6 = Y8_GPIO_NUM;
  config.pin_d7 = Y9_GPIO_NUM;
  config.pin_xclk = XCLK_GPIO_NUM;
  config.pin_pclk = PCLK_GPIO_NUM;
  config.pin_vsync = VSYNC_GPIO_NUM;
  config.pin_href = HREF_GPIO_NUM;
  config.pin_sccb_sda = SIOD_GPIO_NUM;
  config.pin_sccb_scl = SIOC_GPIO_NUM;
  config.pin_pwdn = PWDN_GPIO_NUM;
  config.pin_reset = RESET_GPIO_NUM;
  config.xclk_freq_hz = 20000000;
  config.frame_size = FRAMESIZE_UXGA;
  config.pixel_format = PIXFORMAT_JPEG; // for streaming
  //config.pixel_format = PIXFORMAT_RGB565; // for face detection/recognition
  config.grab_mode = CAMERA_GRAB_WHEN_EMPTY;
  config.fb_location = CAMERA_FB_IN_PSRAM;
  config.jpeg_quality = 12;
  config.fb_count = 1;
  
  // if PSRAM IC present, init with UXGA resolution and higher JPEG quality
  //                      for larger pre-allocated frame buffer.
  if(config.pixel_format == PIXFORMAT_JPEG){
    if(psramFound()){
      config.jpeg_quality = 10;
      config.fb_count = 2;
      config.grab_mode = CAMERA_GRAB_LATEST;
    } else {
      // Limit the frame size when PSRAM is not available
      config.frame_size = FRAMESIZE_SVGA;
      config.fb_location = CAMERA_FB_IN_DRAM;
    }
  } else {
    // Best option for face detection/recognition
    config.frame_size = FRAMESIZE_240X240;
#if CONFIG_IDF_TARGET_ESP32S3
    config.fb_count = 2;
#endif
  }

#if defined(CAMERA_MODEL_ESP_EYE)
  pinMode(13, INPUT_PULLUP);
  pinMode(14, INPUT_PULLUP);
#endif

  // camera init
  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) {
    Serial.printf("Camera init failed with error 0x%x", err);
    return;
  }

  sensor_t * s = esp_camera_sensor_get();
  // initial sensors are flipped vertically and colors are a bit saturated
  if (s->id.PID == OV3660_PID) {
    s->set_vflip(s, 1); // flip it back
    s->set_brightness(s, 1); // up the brightness just a bit
    s->set_saturation(s, -2); // lower the saturation
  }
  // drop down frame size for higher initial frame rate
  if(config.pixel_format == PIXFORMAT_JPEG){
    s->set_framesize(s, FRAMESIZE_QVGA);
  }

#if defined(CAMERA_MODEL_M5STACK_WIDE) || defined(CAMERA_MODEL_M5STACK_ESP32CAM)
  s->set_vflip(s, 1);
  s->set_hmirror(s, 1);
#endif

#if defined(CAMERA_MODEL_ESP32S3_EYE)
  s->set_vflip(s, 1);
#endif

// Setup LED FLash if LED pin is defined in camera_pins.h
#if defined(LED_GPIO_NUM)
  //setupLedFlash(LED_GPIO_NUM);
#endif

  WiFi.begin(ssid, password);
  WiFi.setSleep(false);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected");

  Serial.print("Camera Ready! IP is:");
  Serial.print(WiFi.localIP());
}
void sendImageWithSequence(uint8_t *imageData, size_t imageSize, uint8_t frameNo, uint8_t packetNo) {
  size_t remainingSize = imageSize;
  
  while (remainingSize > 0) {
    size_t chunkSize = std::min(remainingSize, (size_t)1448);
    
    udp.beginPacket(serverIp, serverPort);
    udp.write(frameNo);  // 상위 바이트
    udp.write(packetNo);  // 하위 바이트
    udp.write(imageData, chunkSize);
    udp.endPacket();

    Serial.println(2 + chunkSize);

    imageData += chunkSize;
    remainingSize -= chunkSize;
    packetNo = (packetNo + 1) % 256;
  }
}

void loop() {
  static uint8_t frameNo = 0;
  camera_fb_t *fb = esp_camera_fb_get();

  uint8_t packetNo = 0;

  sendImageWithSequence(fb->buf, fb->len, frameNo, packetNo);

  esp_camera_fb_return(fb);
  Serial.println("");
  Serial.print("send image to Server Size: ");
  Serial.println(fb->len);
  frameNo = (frameNo + 1) % 256;
  delay(1000); // 1초 대기
}

 

 

 

import socket
from io import BytesIO

UDP_IP = "0.0.0.0"  # ESP32의 IP 주소
UDP_PORT = 12345     # ESP32에서 설정한 포트 번호

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind((UDP_IP, UDP_PORT))

combined_data = b""  # 이미지 데이터를 합칠 변수

while True:
    data, addr = sock.recvfrom(1450)  # 데이터 수신 (최대 1000바이트)
    
    frame_no = data[0]    # 첫 번째 바이트는 frameNo
    packet_no = data[1]   # 두 번째 바이트는 packetNo
    image_data = data[2:]  # 나머지는 이미지 데이터

    print(f"Received Frame: {frame_no}, Packet: {packet_no}, Image Size: {len(image_data)} bytes")

    # 이미지 데이터를 합칩니다.
    combined_data += image_data

    # 패킷 크기가 1000보다 작으면서 frame_no 값이 같고, packet_no 보다 작은 패킷들을 합칩니다.
    if len(image_data) < 1448 and packet_no > 0:
        # 합쳐진 이미지 데이터를 파일로 저장합니다.
        with open(f"received_image_frame_{frame_no}.jpg", "wb") as file:
            file.write(combined_data)

        # 이미지 처리가 끝나면 combined_data 초기화
        combined_data = b""

 


 

서버측 코드를 아래와 같이 바꾸면 실시간으로 받는 이미지를 OPENCV 라이브러리에서 지원하는 IMSHOW로 출력한다

ESP32-CAM에서 빠른 속도로 보내서 1초에 15~30FRAME 정도로 송신해주면 받는 쪽에서 이미지가 빠르게 바뀌니까 사람은 동영상으로 착각하게 됨.

보내는 속도를 조절하고 싶다? => ARDUINO 코드 제일 하단의 loop 함수 끝의 delay(1000)의 값을 변경해주면 된다.

 

66을 넣어주면 1초에 15frame이고 33을 넣어주면 1초에 30frame을 전송하게 된다.

각자의 네트워크 환경이나 사용 목적에 맞는 frame rate를 찾아서 적용해주면 되겠다.

 

import socket
import cv2
import numpy as np

UDP_IP = "0.0.0.0"  # ESP32의 IP 주소
UDP_PORT = 12345     # ESP32에서 설정한 포트 번호

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind((UDP_IP, UDP_PORT))

combined_data = b""  # 이미지 데이터를 합칠 변수

while True:
    data, addr = sock.recvfrom(1450)  # 데이터 수신 (최대 1000바이트)
    
    frame_no = data[0]    # 첫 번째 바이트는 frameNo
    packet_no = data[1]   # 두 번째 바이트는 packetNo
    image_data = data[2:]  # 나머지는 이미지 데이터

    print(f"Received Frame: {frame_no}, Packet: {packet_no}, Image Size: {len(image_data)} bytes")

    # 이미지 데이터를 합칩니다.
    combined_data += image_data

    # 패킷 크기가 1000보다 작으면서 frame_no 값이 같고, packet_no 보다 작은 패킷들을 합칩니다.
    if len(image_data) < 1448 and packet_no > 0:
        # 디코딩된 이미지를 표시합니다.
        try:
            img_array = np.frombuffer(combined_data, dtype=np.uint8)
            img = cv2.imdecode(img_array, cv2.IMREAD_COLOR)
            
            if img is not None and img.size != 0:
                cv2.imshow("Received Image", img)
                cv2.waitKey(1)  # 잠시 대기 후 다음 프레임 수신
            else:
                print("Error decoding image. Image is None or empty.")
        except Exception as e:
            print(f"Error during decoding: {e}")
            print(f"Size of combined_data: {len(combined_data)} bytes")

        # 이미지 처리가 끝나면 combined_data 초기화
        combined_data = b""

 

 


udp로 패킷을 송신할 땐 순서대로 송신하였더라도, 수신하다 보면 항상 원하는 순서대로 수신받는게 아니기 때문에, 순서제어를 추가해줬던 것인데, 윗 코드들은 한 이미지에 대해서 중간에 패킷이 빠져있을 때 안합치고 넘어가는 로직이 없었음.
그래서 아래 코드를 보면 1500보다 작은 크기의 패킷이 들어와서 해당 이미지의 패킷들을 합치려고 할 때, 만약에 패킷 번호 중 하나의 데이터가 비어있다면 그 이미지는 버리고 다음 이미지를 띄우는 방식으로 구현하였음.

어차피 빠른 속도로 들어오고 있기 때문에 네트워크 환경이 너무 나쁘지만 않다면 1초에 나오는 30frame 중 1~2개 빠진다고 해서 실시간 영상처럼 보이는데 문제가 되지 않을 거라고 판단하였음.

 

import socket
import cv2
import numpy as np

UDP_IP = "0.0.0.0"  # ESP32의 IP 주소
UDP_PORT = 12345     # ESP32에서 설정한 포트 번호

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind((UDP_IP, UDP_PORT))

combined_data = b""  # 이미지 데이터를 합칠 변수
temp_data = [b"" for _ in range(10)]

while True:
    data, addr = sock.recvfrom(1450)  # 데이터 수신 (최대 1000바이트)
    
    frame_no = data[0]    # 첫 번째 바이트는 frameNo
    prev_frame_no = frame_no
    packet_no = data[1]   # 두 번째 바이트는 packetNo
    image_data = data[2:]  # 나머지는 이미지 데이터

    print(f"Received Frame: {frame_no}, Packet: {packet_no}, Image Size: {len(image_data)} bytes")

    # 이미지 데이터를 합칩니다.
    temp_data[packet_no] += image_data
    # 패킷 크기가 1450보다 작으면서 frame_no 값이 같고, packet_no 보다 작은 패킷들을 합칩니다.
    if len(image_data) < 1448 and packet_no > 0:
        # 디코딩된 이미지를 표시합니다.
        try:
            for index in range(packet_no):
                if len(temp_data[index]) == 0:
                    temp_data = [b"" for _ in range(10)]
                    combined_data = b""
                    continue
                combined_data += temp_data[index]
                #udp 순서가 꼬여서 만약 해당되는 index가 비어있을 경우 그 프레임은 버림
                img_array = np.frombuffer(combined_data, dtype=np.uint8)
                img = cv2.imdecode(img_array, cv2.IMREAD_COLOR)
            
            if img is not None and img.size != 0:
                cv2.imshow("Received Image", img)
                cv2.waitKey(1)  # 잠시 대기 후 다음 프레임 수신
            else:
                print("Error decoding image. Image is None or empty.")
        except Exception as e:
            print(f"Error during decoding: {e}")
            print(f"Size of combined_data: {len(combined_data)} bytes")

        # 이미지 처리가 끝나면 combined_data 초기화
        combined_data = b""
        temp_data = [b"" for _ in range(10)]

 

 

오케이 그럼 파이썬 코드로 feasibility를 빠르게 확인했으니,

 

다음 글엔 파이썬 코드를 C++로 변환해서 올리겠슴둥~

 

여러분도 feasibility 확인용으로 파이썬으로 빠르게 동작만 확인하고 자신이 사용할 언어로 converting하는 과정을 가지시면 좋슴다~

 

아래 다음 글 링크 있슴다...!

 

 

 

[Firmware Programming] C++ ESP32-CAM 보드 UDP camera frame 패킷 순서 제어 및 실시간 전송

이전글을 아래 링크 참고 [Firmware Programming] ESP32-CAM 보드 UDP camera frame 패킷 순서 제어 및 실시간 전송 ESP32-CAM 연결 및 업로드, udp 통신방식은 각자 이해가 된 상태라고 가정하고 진행합니다. 참고

program-developers-story.tistory.com

 

 

 

 

 

 

 

반응형