본문 바로가기

[INTERFACE]/WIZNET_EVM

W5300 을 이용한 네트워크 카메라 구현 3/3 - 카메라 이미지 출력 및 영상처리

 

개요

[SRM32F4-RP] 보드에서 W5300을 제어 하고 고속 TCP전송 테스트 가 완료 되었으므로 네트워크를 통해 카메라 영상을 전송하는 테스트를 진행 해 보자.

 

STM32의 카메라 인터페이스 DCMI를 이용하여 카메라 영상을 얻어 네트워크로 이미지 영상을 보내고 PC 호스트 프로그램에서 영상을 받아서 간단히 영상 처리 할수 있는 프로그램을 작성 할것이다.

 

W5300을 이용하여 TCP 전송률 테스트 결과 60Mbps 이상 전송 가능하므로 640x480x2 Byte RAW(RGB565) 이미지 데이터를 초당 10장을 전송할 수 있다. RAW Data를 사용하는 이유는 영상 처리에 있어 최대한 손실을 없도록 하기 위해서 이다.

 

640x480 해상도의 RAW 이미지를 초당 10프레임 보내어 출력하고 간단한 영상 처리 까지 하는 것을 목표로 네트워크 카메라를 제작 해 보자

 

프로젝트 관련 전체 소스코드는 https://github.com/elabsystem/STM32_RP 에서 다운로드할수있다.





 

카메라 연결

STM32에서 카메라는 DCMI 장치에서 설정 할 수 있다. STM32CubeIDE 를 이용하여 DCMI설정을  해 준다.

 




인터럽트를 사용할 수 있도록 설정하고 DMA 설정도 해 준다.

 



카메라 모듈에는 Master 클럭을 입력해 주어야 하는데 STM32의 PA8을 MCO로 설정하여 16Mhz의 시스템 클럭이 출력 되도록 해 주었다.

 

 




카메라관련 설정은 I2C 인터페이스로 가능하고 I2C2를 활성화 하도록 했다.

 



설정을 완료후 저장하면 기본 코드가 생성된다.



 

FIRMWARE

카메라 제어 관련 코드는 STMicro에서 제공하는 ov2640.c 파일(https://github.com/STMicroelectronics/stm32-ov2640) 을 이용하였다.



카메라 관련 함수는 _user_code_cam.h 에 작성되어 있다.

카메라에서 라인데이터가 들어올 때 HAL_DCMI_LineEventCallback() 함수와 한프레임이 완료 되었을때 HAL_DCMI_VsyncEventCallback() 함수를 재 정의 해서 main 함수에 알려 주도록 했다.

 

void HAL_DCMI_LineEventCallback(DCMI_HandleTypeDef *hdcmi)
{
	gCameraLineFlag++;
}

void HAL_DCMI_VsyncEventCallback(DCMI_HandleTypeDef *hdcmi)
{
	gCameraFrameFlag++;
}


void CamImagTransferLine(unsigned int Cmd, unsigned int Para)
{

  	int ret ;

	unsigned char data_buf[2048*_UDP_TX_BUF_RATE];
	unsigned int size = _IMAGE_SIZE_X*2*_UDP_TX_BUF_RATE;

  	data_buf[0] = Cmd;
  	data_buf[1] = 0;
  	data_buf[2] = Para>>8;
  	data_buf[3] = Para;

  	memcpy((uint8_t *)&data_buf[4], (uint8_t *)&FrameBuffer, size);

	if(gImgTransferFlag)
	{
		ret = send(_CAM_SOCK_NUM, (uint8_t *)data_buf, size+4);
	}
}

void CamImagTransferFrame(unsigned int Cmd, unsigned int Para)
{
	int ret ;
	unsigned char data_buf[2048*_UDP_TX_BUF_RATE];
	unsigned int size = _IMAGE_SIZE_X*2*_UDP_TX_BUF_RATE;


  	data_buf[0] = Cmd;
  	data_buf[1] = 0;
  	data_buf[2] = Para>>8;
  	data_buf[3] = Para;


	if(gImgTransferFlag)
	{
		HAL_Delay(1);
		ret = send(_CAM_SOCK_NUM, data_buf, size+4);
		HAL_Delay(1);
	}
}

 

 

 

메인 루프에서 라인 인터럽트 신호가 인식되면 카메라 버퍼에 저장되어 있는 영상 데이터를 TCP 로 전송하고 프레임 인터럽트 신호가 인식되면  PC 호스트 프로그램에 프레임 완료 신호를 전송해서 화면을 갱신하도록 했다.

 

  while (1)
  {
	/* USER CODE END WHILE */
	ProcessCamTcps(_CAM_SOCK_NUM, ethBuf0, dDestport);

	if(gCameraFrameFlag>0)
	{
		gCameraFrameFlag = 0;
		CamImagTransferFrame(_CMD_CAM_FRAME, gLineCnt);

		printf(">%d, %d\r\n", gFrameCnt, gLineCnt);
		gFrameCnt++;
		gLineCnt = 0;
	}


	if(gCameraLineFlag>0)
	{
		gCameraLineFlag = 0;

		CamImagTransferLine(_CMD_CAM_LINE, gLineCnt);
		gLineCnt++;
	}
	/* USER CODE BEGIN 3 */
  }

 

 

 

PC에서 전송되는 명령을 처리하는 부분은 ProcessCamTCPS() 함수에서 처리하고 있다.

TCP 통신으로 영상 전송 시작 및 종료나 카메라 제어와 같은 명령을 전송할수 있다.

 

int32_t ProcessCamTCPS(uint8_t sn, uint8_t* buf, uint16_t port)
{
   int32_t ret;
   uint16_t size = 0, sentsize=0;

	unsigned int cmd;
	unsigned int para;

   switch(getSn_SR(sn))
   {
      case SOCK_ESTABLISHED :
         if(getSn_IR(sn) & Sn_IR_CON)
         {
			setSn_IR(sn,Sn_IR_CON);
         }
         
         // Don't need to check SOCKERR_BUSY because it doesn't not occur.
		 if((size = getSn_RX_RSR(sn)) > 0) 
         {
			if(size > ETH_MAX_BUF_SIZE) size = ETH_MAX_BUF_SIZE;
			ret = recv(sn, buf, size);

			cmd = buf[0];
			para = buf[2]<<8 | buf[3];

            if(cmd == _CMD_LED)
            {
            	if(para == 0)
           		{
            		Led1Off();
           		}
            	else if(para == 1)
            	{
            		Led1On();
            	}
            }
            else if(cmd == _CMD_CAM_START)
            {
            	gImgTransferFlag = 1;
            	printf("Cam Stream Start\r\n");
            }
            else if(cmd == _CMD_CAM_STOP)
            {
            	gImgTransferFlag = 0;
            	printf("Cam Stream Stop\r\n");
            }

			//sendto(sock, data_buf, len, gDestip, dDestport);

			printf("rx size=%d	\r\n", size);
         }
         break;
      case SOCK_CLOSE_WAIT :

         if((ret = disconnect(sn)) != SOCK_OK) return ret;
         break;
      case SOCK_INIT :
         if( (ret = listen(sn)) != SOCK_OK) return ret;
         break;
      case SOCK_CLOSED:

         if((ret = socket(sn, Sn_MR_TCP, port, 0x00)) != sn) return ret;

         break;
      default:
         break;
   }
   return 1;
}

 

 

 

PC 소프트웨어

W5300 보드에서 영상 데이터를 보내면 데이터를 받을 수 있는 Client 프로그램이 필요 한다. Visual Studio C++와 OpenCV 라이브러리를 이용하여 PC에서 영상을 출력하는 NetImagPlay 프로그램을 작성 하였다.

 

OpenCV 는 https://opencv.org/releases/ 에서 다운로드 받아서 설치 하고 Visual Studio 환경설정에서 관련 파일의 디렉토리 를 설정 해야 한다. 

 

OpenCV 라이브러리 파일도 설정해 준다. 



 

네트워크 정보를 입력할 수 있도록 설정 하는 PC 프로그램

실행 파일은 https://github.com/elabsystem/STM32_RP/tree/master/HostSW_NetPlay/x64/Release

에서 다운로드 가능하며 OpenCV가 설치 되어 있지 않다면  opencv_world430.dll 파일이 필요 하다.




 

TCP로 데이터를 전송할수 있는 함수 SendCommand()를  작성해서 제어 명령을 전송 할 수 있다.

 

int CNetImagePlayDlg::SendCommand(unsigned int Cmd, unsigned int Para)
{
  CString str;
  unsigned char buf[4];

  buf[0] = Cmd;
  buf[1] = 0;
  buf[2] = Para >> 8;
  buf[3] = Para;

  m_ClientSocket.Send(buf, sizeof(buf));

  return 0;
}



W5300 에서 TCP로 영상 데이터를 전송하면 OnReceive()함수가 호출되고 라인과 프레임 정보를 구분하여 화면에 출력 한다. 

int CNetImagePlayDlg::OnReceive(unsigned char* pDataBuffer)
{
  // TODO: 여기에 구현 코드 추가.
  CString str;

  int cmd;
  int line = 0;

  unsigned int size = _IMAGE_SIZE_X * 2 * _UDP_TX_BUF_RATE;
  cmd = pDataBuffer[0];
  line = pDataBuffer[2] << 8 | pDataBuffer[3];


  if (cmd == _CMD_CAM_LINE)
  {
    str.Format(L"%d, %d", m_gCamBufferIndex, line);
    SetDlgItemText(IDC_STATIC_MSG, str);

    if (m_gCamBufferIndex < _IMAGE_SIZE_Y)
    {
      memcpy((unsigned char*)&gImageBuffer[m_gCamBufferIndex * _IMAGE_SIZE_X * 2 * _UDP_TX_BUF_RATE], (unsigned char*)&pDataBuffer[4], _IMAGE_SIZE_X * 2 + _UDP_TX_BUF_RATE);
      m_gCamBufferIndex++;
    }
  }
  else if (cmd == _CMD_CAM_FRAME)
  {
  	m_gCamBufferIndex = 0;

 	 DisplayCamImag(gImageBuffer, _IMAGE_SIZE_Y, _IMAGE_SIZE_X);
  }

  return 0;
}


 

 

영상이 들어 오면 화면에 출력하고 간단한 영상 처리(Canny Edge) 해서 출력 하는 함수

int CNetImagePlayDlg::OnThreadProc()
{
	Mat scale_img;
	int i;

	Mat gray_image;

	//convert image from grey to color cvtColor(m_DisplayImg, gray_image, COLOR_BGR2GRAY); 
	cv::waitKey(10);
	imshow("Sensor", m_DisplayImg);
	cvtColor(m_DisplayImg, gray_image, COLOR_BGR2GRAY);

	Mat img_edge;
	Canny(gray_image, img_edge, 50, 150);

	imshow("img_edge", img_edge);

	return 0;
}

 

 

 

테스트 동영상

W5300을 이용한 네트워크 카메라 테스트 동영상
640x480이미지를 초당 10장 전송하는 (6Mbyte/s - 640x480x2x10) 네트웍크 카메라 구현

https://youtu.be/yE7jFu9kHs4