본문 바로가기
코스웨어/15년 스마트컨트롤러

20151124 - 홍준모 - 비트맵 뷰어 마지막, VFW 1일 차 : 원본 사진을 RGB 값 나누어 영상 처리

by 알 수 없는 사용자 2015. 11. 24.
728x90
반응형

#include <windows.h>
//오른쪽으로 띄울 공간
#define  X_POS    20      
#define  Y_POS    20    

//스태틱의 가로, 세로 크기
#define  S_WIDTH    200    
#define  S_HEIGHT  25

//줄과 줄 사이의 공간(세로)
#define  X_GAP    5  
#define  Y_GAP    5

//그래프 가로, 세로
#define  X_GRAPH_SIZE  256
#define  Y_GRAPH_SIZE  256

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
HINSTANCE g_hInst;
LPSTR lpszClass = L"BmpViewer";

LRESULT On_Destroy(WPARAM, LPARAM);
LRESULT On_Create(WPARAM wParam, LPARAM lParam);
LRESULT On_Paint(WPARAM wParam, LPARAM lParam);
void PrintBmpInfo();
void DrawBmp();//memDC에 원본 그림 복사;

typedef  struct _stMsgMap
{
  UINT uiMsg;
  LRESULT(*fp)(WPARAM, LPARAM);
}StMsgMap;

StMsgMap msgMap[] =
{
  { WM_DESTROY, On_Destroy },
  { WM_CREATE, On_Create },
  { WM_PAINT, On_Paint },
  { WM_NULL, 0 }
};

BITMAPFILEHEADER stBfHead;
BITMAPINFOHEADER stBinfoHead;

unsigned char *ucpData;      //동적할당용
unsigned int uiPad;
unsigned int uiPad2;

HWND hWnd;
HWND hHandle;

static HBITMAP hbmScreen;
static HBITMAP hbmHisto;

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance
  , LPSTR lpszCmdParam, int nCmdShow)
{
  MSG Message;
  WNDCLASS WndClass;
  g_hInst = hInstance;

  WndClass.cbClsExtra = 0;
  WndClass.cbWndExtra = 0;
  WndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
  WndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
  WndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
  WndClass.hInstance = hInstance;
  WndClass.lpfnWndProc = (WNDPROC)WndProc;
  WndClass.lpszClassName = lpszClass;
  WndClass.lpszMenuName = NULL;
  WndClass.style = CS_HREDRAW | CS_VREDRAW;
  RegisterClass(&WndClass);

  hWnd = CreateWindow(lpszClass, lpszClass, WS_OVERLAPPEDWINDOW,
    CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
    NULL, (HMENU)NULL, hInstance, NULL);

  ShowWindow(hWnd, nCmdShow);

  while (GetMessage(&Message, 000)) 
  {
    TranslateMessage(&Message);
    DispatchMessage(&Message);
  }

  return Message.wParam;
}

LRESULT CALLBACK WndProc(HWND hWpWnd, UINT iMessage, WPARAM wParam, LPARAM lParam)
{
  StMsgMap *stpMap = msgMap;

  hWnd = hWpWnd;

  while ((*stpMap).uiMsg != WM_NULL)
  {
    if (iMessage == (*stpMap).uiMsg)
    {
      ((*stpMap).fp)(wParam, lParam);

      return 0;
    }

    ++stpMap;
  }

  return(DefWindowProc(hWpWnd, iMessage, wParam, lParam));
}

LRESULT On_Create(WPARAM wParam, LPARAM lParam)//동적활당
{
  HANDLE hFile;  // 한번 읽고 닫기 때문에 static x
  BOOL bRet;
  DWORD dwCount;

  //bmp read
  hFile = CreateFile(L"C:\\aa.bmp", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
  if (hFile == INVALID_HANDLE_VALUE)
  {
    MessageBox(hWnd, L"On_Create()의 CreateFile() 에러.", L"Button", MB_OK);
    PostQuitMessage(0);
    return 0;
  }
  //헤더 read
  bRet = ReadFile(hFile, &stBfHead, sizeof(BITMAPFILEHEADER), &dwCount, NULL);
  if (bRet == FALSE)
  {
    CloseHandle(hFile);
    MessageBox(hWnd, L"On_Create()의 ReadFile() 에러. - BITMAPFILEHEADER", L"Button", MB_OK);
    PostQuitMessage(0);
    return 0;
  }
  bRet = ReadFile(hFile, &stBinfoHead, sizeof(BITMAPINFOHEADER), &dwCount, NULL);
  if (bRet == FALSE)
  {
    CloseHandle(hFile);
    MessageBox(hWnd, L"On_Create()의 ReadFile() 에러. - BITMAPINFOHEADER", L"Button", MB_OK);
    PostQuitMessage(0);
    return 0;
  }
  //패딩 값 계산
  uiPad = stBinfoHead.biWidth % 4;
  uiPad2 = stBinfoHead.biHeight % 4;

  //일반화
  stBinfoHead.biSizeImage = stBinfoHead.biWidth * (stBinfoHead.biHeight * 3 + stBinfoHead.biHeight % 4);

  //헤더 print
  PrintBmpInfo();

  //bmp heap memory copy
  ucpData = malloc(stBinfoHead.biSizeImage);
  if (ucpData == 0)
  {
    CloseHandle(hFile);
    MessageBox(hWnd, L"On_Create()의 malloc() 에러.", L"Button", MB_OK);
    PostQuitMessage(0);
    return 0;
  }
  bRet = ReadFile(hFile, ucpData, stBinfoHead.biSizeImage, &dwCount, NULL);
  if (bRet == FALSE)
  {
    CloseHandle(hFile);
    free(ucpData);
    MessageBox(hWnd, L"On_Create()의 ReadFile() 에러. - .bmp 파일 읽기 실패", L"Button", MB_OK);
    PostQuitMessage(0);
    return 0;
  }
  
  //hbmScreen, hbmHisto 에 원본 복사
  DrawBmp();
  //draw_Bitmap();

  MessageBox(hWnd, L"On_Create()가 성공적으로 호출 되었습니다.", L"Button", MB_OK);
  CloseHandle(hFile);

  return 0;
}

LRESULT On_Destroy(WPARAM wParam, LPARAM lParam)
{
  PostQuitMessage(0);

  return 0;
}

LRESULT On_Paint(WPARAM wParam, LPARAM lParam)
{
  HDC hdc;
  HDC memDC;
  PAINTSTRUCT ps;

  hdc = BeginPaint(hWnd, &ps);
  
  memDC = CreateCompatibleDC(hdc);
  SelectObject(memDC, hbmScreen);
  BitBlt(hdc, X_POS, Y_POS, stBinfoHead.biWidth, stBinfoHead.biHeight, memDC, 00, SRCCOPY);
  DeleteDC(memDC);

  memDC = CreateCompatibleDC(hdc);
  SelectObject(memDC, hbmHisto);
  BitBlt(hdc, X_POS + (stBinfoHead.biWidth) + (X_GAP * 2) + (S_WIDTH * 2), Y_POS, X_GRAPH_SIZE + 1, Y_GRAPH_SIZE, memDC, 00, SRCCOPY);
  DeleteDC(memDC);

  EndPaint(hWnd, &ps);

  //InvalidateRect(hWnd, &ps, FALSE);  //이 부분이 애매..

  return 0;
}

void PrintBmpInfo()
{
  int iYCount;
  WCHAR *ucTitle[30=
  {
    L"Magic Number    :",
    L"
File Byte Size    :",
    L"
Data Position    :",
    L"
Info Header Size    :",
    L"
Width Size    :",
    L"
Height Size    :",
    L"
Bit Planes Number    :",
    L"
Pixel Bit Count    :",
    L"
Compression Boolen  :",
    L"
Image Size    :",
    L"
X Pelsper Meter    :",
    L"
Y Pelsper Meter    :",
    L"
Used Color Number    :",
    L"
Important Color Index  :",
  };
  WCHAR ucValue[14][30];
  
  wsprintf(ucValue[0], L"
[%c][%c]", *(((unsigned char *)(&stBfHead)) + 0), *(((unsigned char *)(&stBfHead)) + 1));
  wsprintf(ucValue[1], L"
[%d] Bytes", stBfHead.bfSize);
  wsprintf(ucValue[2], L"%d", stBfHead.bfOffBits);
  wsprintf(ucValue[3], L"%d", stBinfoHead.biSize);
  wsprintf(ucValue[4], L"[%d] pixel", stBinfoHead.biWidth);
  wsprintf(ucValue[5], L"[%d] pixel", stBinfoHead.biHeight);
  wsprintf(ucValue[6], L"%d", stBinfoHead.biPlanes);
  wsprintf(ucValue[7], L"%d", stBinfoHead.biBitCount);
  /*
  switch (stBinfoHead.biCompression)
  {
    case 0:
      wsprintf(ucValue[8], L"[BI_RGB]");
      break;
    case 1:
      wsprintf(ucValue[8], L"[BI_RLE8]");
      break;
    case 2:
      wsprintf(ucValue[8], L"[BI_RLE4]");
      break;
    case 3:
      wsprintf(ucValue[8], L"[BI_BITFIELDS]");
      break;
    default:
      break;
  }
  */


  wsprintf(ucValue[8], L"%d", stBinfoHead.biCompression);
  wsprintf(ucValue[9], L"[%d] Bytes", stBinfoHead.biSizeImage);
  wsprintf(ucValue[10], L"%d", stBinfoHead.biXPelsPerMeter);
  wsprintf(ucValue[11], L"%d", stBinfoHead.biYPelsPerMeter);
  wsprintf(ucValue[12], L"%d", stBinfoHead.biClrUsed);
  wsprintf(ucValue[13], L"%d", stBinfoHead.biClrImportant);

  //출력
  for (iYCount = 0; iYCount < 14; ++iYCount)
  {
    CreateWindow(
      L"static",
      ucTitle[iYCount],
      WS_CHILD | WS_VISIBLE,
      (stBinfoHead.biWidth) + X_POS, Y_POS + (S_HEIGHT*iYCount) + (Y_GAP*iYCount), S_WIDTH, S_HEIGHT,
      hWnd,
      (HMENU)-1,
      g_hInst,
      NULL
      );

    hHandle = CreateWindow(
      L"edit",
      ucValue[iYCount],
      WS_CHILD | WS_VISIBLE | WS_BORDER | ES_AUTOHSCROLL,
      (stBinfoHead.biWidth) + X_POS + S_WIDTH + X_GAP, (Y_POS + (S_HEIGHT * iYCount) + (Y_GAP * iYCount)), S_WIDTH, S_HEIGHT,
      hWnd,
      (HMENU)-1,
      g_hInst,
      NULL
      );
  }

  return;
}

void DrawBmp()//hbmScreen에 원본 그림 복사
{
  HDC hdc;
  HDC memDC;
  unsigned int uiCount[256= { 0, };
  int iCountY; 
  int iCountX;
  unsigned int uiMaxVal;  //그래프의 세로 축 최대 값을 구하기 위함.

  hdc = GetDC(hWnd);

  //bmp 사진 크기, 그래프 사진 크기 만큼 할당
  hbmScreen = CreateCompatibleBitmap(hdc, stBinfoHead.biWidth, stBinfoHead.biHeight);
  hbmHisto = CreateCompatibleBitmap(hdc, X_GRAPH_SIZE + 1, Y_GRAPH_SIZE + 1);
  memDC = CreateCompatibleDC(hdc);

  SelectObject(memDC, hbmScreen);
  
  //ucpData, 즉 불러들인 bmp 파일로부터 memDC를 활용하여 hbmScreen에 원본 비트맵 파일 저장. 
  //( stBinfoHead.biWidth - iCountY 함으로써 현재 리틀엔디안으로 상이 뒤집힌 상태로 저장된 것을 올바르게 다시 저장) 
  for (iCountY = 0; iCountY < stBinfoHead.biHeight; ++iCountY)
  {
    for (iCountX = 0; iCountX < stBinfoHead.biWidth; ++iCountX)
    {
      SetPixel(memDC, iCountX, stBinfoHead.biHeight - iCountY, RGB(*(ucpData + ((iCountY*stBinfoHead.biWidth) * 3) + ((iCountX * 3) + 2) + (iCountY*uiPad)),
                                    *(ucpData + ((iCountY*stBinfoHead.biWidth) * 3) + ((iCountX * 3) + 1) + (iCountY*uiPad)),
                                    *(ucpData + ((iCountY*stBinfoHead.biWidth) * 3) + ((iCountX * 3) + 0) + (iCountY*uiPad))));
      ++uiCount[ucpData[((iCountY * stBinfoHead.biWidth) * 3) + ((iCountX * 3) + 2) + (iCountY * uiPad)]];  //R, 빈도 수 계산 ex) 0 값 몇개 ~ 1 값 몇개 ~
      ++uiCount[ucpData[((iCountY * stBinfoHead.biWidth) * 3) + ((iCountX * 3) + 1) + (iCountY * uiPad)]];  //G, 빈도 수 계산
      ++uiCount[ucpData[((iCountY * stBinfoHead.biWidth) * 3) + ((iCountX * 3) + 0) + (iCountY * uiPad)]];  //B, 빈도 수 계산
    }
  }
  
  uiMaxVal = 0;
  for (iCountX = 0; iCountX < X_GRAPH_SIZE; ++iCountX)
  {
    uiCount[iCountX] = uiCount[iCountX] / 3;
    if (uiMaxVal < uiCount[iCountX])
    {
      uiMaxVal = uiCount[iCountX];  // 맥스 값 구함.
    }
  }
  
  for (iCountX = 0; iCountX < X_GRAPH_SIZE; ++iCountX)
  {
    uiCount[iCountX] = (uiCount[iCountX] * Y_GRAPH_SIZE) / uiMaxVal;  // 그래프의 세로(상한선)가 255를 못 넘게 끔 만든다. 이때 숫자를 키운뒤 나누면 오차율이 줄어든다. 
  }// 이제 uiCount 는 높이 값을 가지게 된다.
  
  DeleteDC(memDC);
  free(ucpData);
  
  //그래프 용 memDC
  memDC = CreateCompatibleDC(hdc);  
  SelectObject(memDC, hbmHisto);

  //그래프 기본 틀 x, y 축 그리기
  PatBlt(memDC, 00, X_GRAPH_SIZE + 1, Y_GRAPH_SIZE, WHITENESS);
  for (iCountY = 0; iCountY < (X_GRAPH_SIZE + 1); ++iCountY)
  {
    for (iCountX = 0; iCountX < (Y_GRAPH_SIZE); ++iCountX)
    {
      if ((Y_GRAPH_SIZE - 1 - iCountY) <= uiCount[iCountX])// y : 255 -> 
      {
        SetPixel(memDC, iCountX + 1, iCountY - 1, RGB(000));
      }
    }
    SetPixel(memDC, 0, iCountY, RGB(000));  //세로 축
  }
  
  DeleteDC(memDC);
  ReleaseDC(hWnd, hdc);

  return;
}

버그 수정 완료.
그래프 쪽 조그만 버그가 있긴한데 우선 나중에 확인 후 다시 올리겠습니다.


일단 영상처리로 넘어가기 위해 기본형 준비.





비디오 캡쳐를 하기 위해서 먼저 vfw32.lib 를 프로젝트에서 포함 해주어야 하며 작성 소스 상단에 Vfw.h를

include 해주어야 한다.

 

비디오 캡쳐를 위한 작업 순서를 간단히 다음과 같다.

 

캡쳐 윈도우를 생성

윈도우와 캡쳐 드라이버를 연결

캡쳐된 비디오 프레임을 캡쳐 윈도우를 통해서 출력

캡쳐된 비디오 프레임을 캡쳐 윈도우에 보여주기 위해서는 한 프레임이 캡쳐 될 때마다 특정 함수를 호출하도록 한 다음에 호출된 함수에서 그 프레임을 화면에 출력한다.

이러한 작업을 수행하기 위해서는 VFW 라이브러리 중에서 다음과 같은 함수들을 사용해야 한다.

capGetDriverDescription()

capCreateCaptureWindow()

capDriverConnect()

capPreviewRate()

capSetVideoFormat()

capDriverDisconnect()

capSetCallbackOnFrame()


 - 함수의 원형

 

HWND VFWAPI capCreateCaptureWindowW (
        LPCWSTR lpszWindowName,
        DWORD dwStyle,
        int x, int y, int nWidth, int nHeight,
        HWND hwndParent, int nID);

이 함수는 캡쳐 윈도우를 생성한다. name 에는 윈도우위 이름을 지정한다. style 윈도우위 스타일을 지정한다. (x,y)에는 캡쳐 윈도우의 좌측 상단의 좌표를 지정한다. width height에는 캡쳐 윈도우의 크기를 지정한다. hWnd에는 부모윈도우의 핸들값을 입력한다. id에는 윈도우의 식별 번호를 입력한다. 캡쳐 윈도우가 정상적으로 생성되면 캡쳐 윈도우의 핸들의 함수 결과값으로 반환되고 그렇지 않으면 NULL값이 반환된다.



BOOL VFWAPI capGetDriverDescriptionW (UINT wDriverIndex,
        LPWSTR lpszName, int cbName,
        LPWSTR lpszVer, int cbVer);

이 함수는 캡쳐 드라이버의 이름 및 버전 정보를 검색한다. 첫 번째 매개변수인 index는 검색하고자 하는 드라이버의 번호를 나타내는데, 0부터 9까지의 값을 가질 수 있다.

즉, 한 컴퓨터에서 9대의 캡쳐 장치가 사용될 수 있다고 가정하고 있다. 검색하고자 하는 번호의 드라이버가 존재하면 이 함수는 name에 드라이버의 이름을 저장하고 version에 드라이버 버전을 저장한 다음에 함수 결과값으로 TURE 값을 반환한다. 

 

capDriverConnect(hWnd, index);

이 함수는 아래 처럼 디파인 되어 있으며

#define capDriverConnect(hwnd, i)          ((BOOL)AVICapSM(hwnd, WM_CAP_DRIVER_CONNECT, (WPARAM)(i), 0L))

AVICapSM 역시 아래 처럼 디파인 되어 있다.( 윈도우 메시지인 WM_CAP_DRIVER_CONNECT 를 사용 하는듯 하다.)

#define AVICapSM(hwnd,m,w,l) ( (::IsWindow(hwnd)) ? ::SendMessage(hwnd,m,w,l) : 0)

이 함수는 캡쳐 윈도우를 캡쳐 드라이버에 연결한다. hWnd는 캡쳐 윈도우의 핸들을 나타내고 index는 캡쳐 드라이버의 번호를 나타낸다. 이 함수는 캡쳐 장치가 정상적으로 작동하여 연결이 성공되면 TURE 값을 반환하고 그렇지 않으면 FALSE 값을 반환한다.



capPreviewRate(hWnd, rate);

이 함수는 아래 처럼 디파인 되어 있으며

#define capPreviewRate(hwnd, wMS)   ((BOOL)AVICapSM(hwnd, WM_CAP_SET_PREVIEWRATE, (WPARAM)(wMS), 0))

AVICapSM 역시 아래 처럼 디파인 되어 있다.( 윈도우 메시지인 WM_CAP_SET_PREVIEWRATE를 사용 하는듯 하다.)

#define AVICapSM(hwnd,m,w,l) ( (::IsWindow(hwnd)) ? ::SendMessage(hwnd,m,w,l) : 0)

이 함수는 미리보기 (preview)모드에서의 프레임 재생 속도를 설정한다. 여기에서 미리보기란 카메라에서 입력된 비디오를 파일에 저장하는 것이 아니라 화면에 보여준다는 것을 의미한다. hWnd는 캡쳐 윈도우의 핸들 값으로 설정하고 rate는 밀리초(ms) 단 위의 시간으로 설정한다. 예를 들어, rate 값을 66으로 설정하면 0.066초마다 새로운 비디오 프레임을 캡쳐해서 디스플레이 하게 도니다. 이와 같은 속도로 재생을 하면 1초에 15개의 비디오 프레임이 디스플레이된다.


capSetVideoFormat(hWnd, videoFormat, videoFormat_size);

#define capSetVideoFormat(hwnd,s,wSize)          ((BOOL)AVICapSM(hwnd,WM_CAP_SET_VIDEOFORMAT, (WPARAM)(wSize), (LPARAM)(LPVOID)(s)))

AVICapSM 역시 아래 처럼 디파인 되어 있다.( 윈도우 메시지인 WM_CAP_SET_VIDEOFORMAT를 사용 하는듯 하다.)

#define AVICapSM(hwnd,m,w,l) ( (::IsWindow(hwnd)) ? ::SendMessage(hwnd,m,w,l) : 0)

이 함수는 캡쳐된 비디오 데이터 형식을 설정한다. 사용자가 원하는 비디오 데이터형식이 캡쳐 장치에서 지원이 되면 이 함수는 TRUE 값을 반환하고 그렇지 않으면 FALSE 값을 반환하므로 반드시 이 함수의 결과값이 TRUE 인지 검사한 다음에 다음단계로 넘어가야 한다.

hWnd는 캡쳐 윈도우의 핸들 값으로 설정한다. videoFormat은 설정하고자 하는 비디오 데이터 형식을 나타내는데, 비디오 데이터의 각 프레임에 대한 비트맵 형식을 BITMAPINFO 구조로 기술한다.

 

BITMAPINFO 구조는 다음과 같다.

typedef struct tagBITMAPINFO{

BITMAPINFOHEADER bmiHeader;

RGBQUAD bmiColrs[1];

BITMAPINFO;  

BITMAPINFO는 BITMAPINFOHEADER와 RGBQUAD로 구성되는데, 여기에서는 다음과 같이 정의되는 BITMAPINFOHEADER 구조체의 값만 사용하면 된다.


pBmiInfo->bmiHeader.biSize = 40;  //BITMAPINFOHEADER 구조체의 크기

pBmiInfo->bmiHeader.biHeight = 480; //영상의 가로크기

pBmiInfo->bmiHeader.biWidth = 640;

//영상의 세로크기(양수:좌측 하단이 원점   음수:좌측 상단이 원점)

pBmiInfo->bmiHeader.biPlanes = 1; //목표 장치의 플레인 수(1로 설정해야함)

pBmiInfo->bmiHeader.biBitCount = (short) 24; //각 픽셀의 비트수

pBmiInfo->bmiHeader.biCompression = 0; //압축 방법(bi_rgb 또는 0:무압축 비트맵)

pBmiInfo->bmiHeader.biSizeImage =0; //비트맵 영상 크기(무압축인 경우 0으로 설정)

pBmiInfo->bmiHeader.biClrImportant = 0; // 비트맵 디스플레이에 사용되는 컬러수

pBmiInfo->bmiHeader.biClrUsed = 0; // 사용된 컬러의 수

pBmiInfo->bmiHeader.biXPelsPerMeter = 10000; //수평 해상도 (미터당 픽셀수)

pBmiInfo->bmiHeader.biYPelsPerMeter = 10000; //수직 해상도 (미터당 픽셀 수)

}BITMAPINFOHEADER;

비디오 캡쳐를 위해서는 다음과 같이 BITMAPINFOHEADER 구조체에서 biSize,biWidth, biHeight, biPlanes, biBitCount 값을 설정하고 나머지 값을은 0으로 설정하면된다.

BITMAPINFO bmi;

memset(&bmi.bmiHeader, 0, sizeof(bmiHeader));

pBmiInfo->bmiHeader.biSize = sizeof(bmi.bmiHeader);

pBmiInfo->bmiHeader.biWidth = 640;//영상의 세로크기(양수:좌측 하단이 원점

pBmiInfo->bmiHeader.biHeight = 480;//영상의 가로크기

pBmiInfo->bmiHeader.biPlanes = 1;//목표 장치의 플레인 수(1로 설정해야함)

pBmiInfo->bmiHeader.biBitCount =  24;//각 픽셀의 비트수


capDriverDisconnect(hWnd);

#define capDriverDisconnect(hwnd)       ((BOOL)AVICapSM(hwnd, WM_CAP_DRIVER_DISCONNECT, (WPARAM)0, 0L))

AVICapSM 역시 아래 처럼 디파인 되어 있다.( 윈도우 메시지인 WM_CAP_DRIVER_DISCONNECT를 사용 하는듯 하다.)

#define AVICapSM(hwnd,m,w,l) ( (::IsWindow(hwnd)) ? ::SendMessage(hwnd,m,w,l) : 0)

이 함수는 carDriverConnect() 함수에 의하여 연결한 캡쳐 윈도우와 캡쳐 장치를 분리하는 함수이다. hWnd에는 분리하고자 하는 캡쳐 윈도우의 핸들 값을 설정한다.


BOOL capSetCallbackOnFrame(hWnd, func);

#define capSetCallbackOnFrame(hwnd, fpProc)        ((BOOL)AVICapSM(hwnd,WM_CAP_SET_CALLBACK_FRAME, 0, (LPARAM)(LPVOID)(fpProc)))

AVICapSM 역시 아래 처럼 디파인 되어 있다.( 윈도우 메시지인 WM_CAP_SET_CALLBACK_FRAME를 사용 하는듯 하다.)

#define AVICapSM(hwnd,m,w,l) ( (::IsWindow(hwnd)) ? ::SendMessage(hwnd,m,w,l) : 0)

VFW 라이브러리에서는 캡쳐된 비디오 프레임을 화면에 보여주는 작업을 callback함수를 사용해서 처리하도록 하고 있다. capSetCallbackOnFrame()함수는 캡쳐 장치로부터 비디오 프레임이 캡쳐되었을때에 이를 화면에 보여주기 위해서 호출되는 callback 함수를 설정한다. hWnd는 캡쳐 윈도우의 핸들 값으로 설정하고 func는 호출될 함수 이름으로 설정한다. 



BOOL capOverlay(hWnd, f);
#define capOverlay(hwnd, f)          ((BOOL)AVICapSM(hwnd, WM_CAP_SET_OVERLAY, (WPARAM)(BOOL)(f), 0L))

#define AVICapSM(hwnd,m,w,l) ( (::IsWindow(hwnd)) ? ::SendMessage(hwnd,m,w,l) : 0)

이 함수는 비디오 오버레이를 사용 할것인지 아닌지를 설정한다. 설정 성공시 TRUE, 그렇지 않을경우 FALSE를 반환.
매개변수는 순서대로 윈도우 핸들, 설정시-true 미설정시 -false
비디오 오버레이를 사용하면 CPU자원이 요구 되지 않아 효과적이라고 한다. 
 
BOOL capPreview(hWnd, f);
#define capPreview(hwnd, f)       ((BOOL)AVICapSM(hwnd, WM_CAP_SET_PREVIEW, (WPARAM)(BOOL)(f), 0L))

#define AVICapSM(hwnd,m,w,l) ( (::IsWindow(hwnd)) ? ::SendMessage(hwnd,m,w,l) : 0)

이 함수는 미리보기 모드를 동작 시킬 것인지 아닌지를 설정한다. 설정을 하게 되면 영상 프레임이 시스템의 메모리로 전달되어 GDI함수를 사용하여 캠처 윈도우에 영상을 출력하게 된다.
매개 변수는 순서대로 윈도우 핸들, 설정시 -true, 미설정시 -false 

 





다음은 캠을 화면에 표시해주는 순서입니다.


① 캡처된 화면을 보여줄 윈도우 영역 지정
m_HostVideo.GetWindowRect(), ScreenToClient()

② 비디오캡처 윈도우를 지정된 영역에 생성 
capCreateCaptureWindow();

③ 캡처 윈도우와 드라이버 연결
capDriverConnect()

④ 캡처윈도우에 프리뷰 모드로 보여줌
capPreview(), capPreviewRate(), capPreviewScale()

여기까지 설정함으로써 호스트 화면에 카메라를 통한 캡처된 이미지 디스플레이

⑤ 콜백 함수의 호환을 위해서 비디오 포맷 설정
capSetVideoFormat(m_hwndCap, &m_BmpInfo, sizeof(BITMAPINFO))

⑥ 캡처되는 이미지의 정보를 얻어냄
capGetVideoFormat(m_hwndCap, &m_BmpInfo, sizeof(BITMAPINFO))

⑦ 콜백 함수를 지정
capSetCallbackOnVideoStream(m_hwndCap, VideoStreamCallbackProc)

⑧ 캡처에 필요한 정보를 셑팅 후 저장
capCaptureSetSetup(m_hwndCap, &captureParms, sizeof(CAPTUREPARMS))

⑨ 캡처링을 실시
capCaptureSequenceNoFile(m_hwndCap)






즉, 여러 정보를 수집하여 짠 프리뷰 코드는


LRESULT On_Create(WPARAM wParam, LPARAM lParam)
{
  HWND hCamera;
  BITMAPINFO bmpInfo;

  //1. 비디오캡처 윈도우를 지정된 영역에 원하는 크기로 생성
  hCamera = capCreateCaptureWindowW(L"Camera", WS_CHILD | WS_VISIBLE, 00320240, hWnd, 0);
  //2. 캡처 윈도우와 드라이버 연결
  capDriverConnect(hCamera, 0);
  //3. 캡처윈도우에 프리뷰 모드로 찍을 프레임 값 설정
  capPreviewRate(hCamera, 48);    //숫자가 작을 수록 화면에 보이는 속도가 빨라진다.
  //4. 캡처되는 이미지의 정보를 얻어냄
  capGetVideoFormat(hCamera, &bmpInfo, sizeof(bmpInfo));
  //5. 원하는 크기로 생성시킨 값으로 수정
  bmpInfo.bmiHeader.biWidth = 320;
  bmpInfo.bmiHeader.biHeight = 240;
  //6. 콜백 함수 호환을 위한 비디오 포맷 설정
  //capSetVideoFormat(hCamera, &bmpInfo, sizeof(bmpInfo));
  //7. 캡쳐 윈도우에 프리뷰 모드로 보여줌
  capPreview(hCamera, TRUE);

  return 0;
}

이와 같으며 실행 결과는,








이와 같다.



이제 화면을 좀 키운다음 다시 해보자.


LRESULT On_Create(WPARAM wParam, LPARAM lParam)
{
  HWND hCamera;
  BITMAPINFO bmpInfo;

  //1. 비디오캡처 윈도우를 지정된 영역에 원하는 크기로 생성
  hCamera = capCreateCaptureWindowW(L"Camera", WS_CHILD | WS_VISIBLE, 00640480, hWnd, 0);
  //2. 캡처 윈도우와 드라이버 연결
  capDriverConnect(hCamera, 0);
  //3. 캡처윈도우에 프리뷰 모드로 찍을 프레임 값 설정
  capPreviewRate(hCamera, 1);    //숫자가 작을 수록 화면에 보이는 속도가 빨라진다.
  //4. 캡처되는 이미지의 정보를 얻어냄
  capGetVideoFormat(hCamera, &bmpInfo, sizeof(bmpInfo));
  //5. 원하는 크기로 생성시킨 값으로 수정
  bmpInfo.bmiHeader.biWidth = 640;
  bmpInfo.bmiHeader.biHeight = 480;
  //6. 콜백 함수 호환을 위한 비디오 포맷 설정
  capSetVideoFormat(hCamera, &bmpInfo, sizeof(bmpInfo));
  //7. 캡쳐 윈도우에 프리뷰 모드로 보여줌
  capPreview(hCamera, TRUE);

  return 0;
}





화면이 커졌다.

프레임 속도를 1로 빠르게 설정해줘야 잘 뜬다. 아마 영상이 커지는 만큼 그에 비하는 속도도 빨라져야 부드러운 영상이 연출 되나 보다.





1단계. 영상 띄우기.

2단계. 콜백 함수로 영상 띄우기.





The VIDEOHDR structure is used by the capVideoStreamCallback function.

Syntax

typedef struct videohdr_tag { LPBYTE    lpData;                    //1바이트 짜리 포인터다.이게 실제 화면 DWORD     dwBufferLength;           

DWORD     dwBytesUsed; DWORD     dwTimeCaptured; DWORD_PTR dwUser; DWORD     dwFlags; DWORD_PTR dwReserved[4]; } VIDEOHDR, *PVIDEOHDR, *LPVIDEOHDR;

Members

lpData

Pointer to locked data buffer.

dwBufferLength

Length of data buffer.

dwBytesUsed

Bytes actually used.

dwTimeCaptured

Milliseconds from start of stream.

dwUser

User-defined data.

dwFlags

The flags are defined as follows.


이걸 활용할듯 하다.


StretchBlt

원형

BOOL StretchBlt(HDC hdcDest, int nXOriginDest, int nYOriginDest, int nWidthDest, int nHeightDest, HDC hdcSrc, int nXOriginSrc, int nYOriginSrc, int nWidthSrc, int nHeightSrc, DWORD dwRop);

인수

▶hDC:비트맵이 복사될 DC

▶X,Y,nWidth,nHeight:비트맵이 복사될 위치의 좌상단 좌표와 폭, 넓이, 이 값은 논리적 좌표값이다.

▶HSrcDC:복사될 비트맵을 가지고 있는 DC. 만약 dwROP가 소스가 필요없는 값이라면 NULL이 될 수도 있다.

▶nXOriginSrc,nYOriginSrc, nWidthSrc, nHeightSrc:복사될 비트맵의 좌측 상단 좌표와 크기. 이 영역의 크기와 복사처의 크기가 다를 경우 비트맵은 복사처의 크기에 맞게 축소되거나 확대된다.

▶dwROP:ROP코드, 즉 브러쉬와 복사원, 복사처의 비트맵 색상이 논리 연산될 방법을 지정한다. 모두 256개의 가능한 값이 있지만 실제로 의미를 갖는 없은 다음 15개이며 매크로 상수가 정의되어 있다. 이 외의 ROP코드가 필요할 경우에는 상수를 직접 사용해야 한다. 아래 표에서 S는 복사원 (Source), D는 복사처(Destination), P는 패턴(Pattern, 즉 브러쉬)를 의미한다.

매크로 상수상수연산식
BLACKNESS0x000000420
DSTINVERT0x00550009~D
MERGECOPY0x00C000CAD&S
MERGEPAINT0x00BB0226~S|D
NOTSRCCOPY0x00330008~S
NOTSRCERASE0x001100A6~(S|D)
PATCOPY0x00F00021P
PATINVERT0x005A0049P^D
PATPAINT0x00FB0A09P|~(S|D)
SRCAND0x008800C6S&D
SRCCOPY0x00CC0020S
SRCERASE0x00440328S&~D
SRCINVERT0x00660046S^D
SRCPAINT0x00EE0086S|D
WHITENESS0x00FF00621
리턴성공하면 nonzero, 에러 발생시 0을 리턴한다.
설명

DC간에 비트맵을 전송하여 복사한다. BitBlt와 동작하는 방식이 유사하나 단 복사원의 크기와 높이를 따로 지정할 수 있기 때문에 확대및 축소 복사할 수 있다. 20*40의 크기를 가지는 비트맵을 40*80영역에 복사하면 이 비트맵은 2배로 확대되며 10*20영역에 복사하면 절반으로 축소된다.

예제 1 

다음 예제는 리소스에 정의된 IDB_BITMAP1을 두배로 확대하여 작업 영역에 출력한다.

#include "resource.h"
LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)
{
	HDC hdc;
	PAINTSTRUCT ps;
	HDC MemDC;
	HBITMAP MyBitmap, OldBitmap;
	int bx,by;
	BITMAP bit;

	switch(iMessage) {
	case WM_PAINT:
		hdc=BeginPaint(hWnd, &ps);
		MemDC=CreateCompatibleDC(hdc);
		MyBitmap=LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP1));
		OldBitmap=(HBITMAP)SelectObject(MemDC, MyBitmap);

		GetObject(MyBitmap,sizeof(BITMAP),&bit);
		bx=bit.bmWidth;
		by=bit.bmHeight;

		StretchBlt(hdc,0,0,bx*2,by*2,MemDC,0,0,bx,by,SRCCOPY);

		SelectObject(MemDC,OldBitmap);
		DeleteObject(MyBitmap);
		DeleteDC(MemDC);
		EndPaint(hWnd, &ps);
		return 0;
	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	}
	return(DefWindowProc(hWnd,iMessage,wParam,lParam));
}

비트맵의 크기 bx, by보다 두배 더 넓은 영역에 출력했으므로 비트맵은 두배로 확대된다.

다음 코드는 비트맵 영역의 절반 크기의 영역에 비트맵을 출력하므로 비트맵이 축소된다.

StretchBlt(hdc,0,0,bx/2,by/2,MemDC,0,0,bx,by,SRCCOPY);

비트맵을 축소할 때는 SetStretchBltMode 함수가 지정한 스트레칭 모드에 따라 획이나 열이 생략된다. 다음 코드는 비트맵을 반대로 뒤집는다.

StretchBlt(hdc,0,by,bx,-by,MemDC,0,0,bx,by,SRCCOPY);

복사원과 복사처의 크기 부호가 다르면 거울에 비친 것처럼 반사된 모양의 비트맵이 출력된다.

참고함수BitBlt:비트맵을 확대하지 않고 출력한다.
플렛폼95이상
본문참조 





StretchDIBits function

The StretchDIBits function copies the color data for a rectangle of pixels in a DIB, JPEG, or PNG image to the specified destination rectangle. If the destination rectangle is larger than the source rectangle, this function stretches the rows and columns of color data to fit the destination rectangle. If the destination rectangle is smaller than the source rectangle, this function compresses the rows and columns by using the specified raster operation.

Syntax

int StretchDIBits(
  _In_       HDC        hdc,
  _In_       int        XDest,
  _In_       int        YDest,
  _In_       int        nDestWidth,
  _In_       int        nDestHeight,
  _In_       int        XSrc,
  _In_       int        YSrc,
  _In_       int        nSrcWidth,
  _In_       int        nSrcHeight,
  _In_ const VOID       *lpBits,
  _In_ const BITMAPINFO *lpBitsInfo,
  _In_       UINT       iUsage,
  _In_       DWORD      dwRop
);

Parameters

hdc [in]

A handle to the destination device context.

XDest [in]

The x-coordinate, in logical units, of the upper-left corner of the destination rectangle.

YDest [in]

The y-coordinate, in logical units, of the upper-left corner of the destination rectangle.

nDestWidth [in]

The width, in logical units, of the destination rectangle.

nDestHeight [in]

The height, in logical units, of the destination rectangle.

XSrc [in]

The x-coordinate, in pixels, of the source rectangle in the image.

YSrc [in]

The y-coordinate, in pixels, of the source rectangle in the image.

nSrcWidth [in]

The width, in pixels, of the source rectangle in the image.

nSrcHeight [in]

The height, in pixels, of the source rectangle in the image.

lpBits [in]

A pointer to the image bits, which are stored as an array of bytes. For more information, see the Remarks section.

lpBitsInfo [in]

A pointer to a BITMAPINFO structure that contains information about the DIB.

iUsage [in]

Specifies whether the bmiColors member of the BITMAPINFO structure was provided and, if so, whether bmiColors contains explicit red, green, blue (RGB) values or indexes. The iUsage parameter must be one of the following values.

ValueMeaning
DIB_PAL_COLORS

The array contains 16-bit indexes into the logical palette of the source device context.

DIB_RGB_COLORS

The color table contains literal RGB values.

https://msdn.microsoft.com/ko-kr/library/windows/desktop/dd145120%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396



SetDIBitsToDevice : 비트맵 전체 또는 일부를 원본 크기 그대로 출력

StretchDIBits : 비트맵을 원하는 크기로 확대 축소 하여 출력



우리는 비트맵 전체를 그대로 출력하는 함수를 활용하여 화면에 그대로 오른쪽에 출력해 볼 것이다.





LRESULT On_Create(WPARAM wParam, LPARAM lParam)
{
  HWND hCamera;
  BOOL bRet;

  //1. 비디오캡처 윈도우를 지정된 영역에 원하는 크기로 생성
  hCamera = capCreateCaptureWindowW(L"Camera", WS_CHILD | WS_VISIBLE, 00, X_SCALE, Y_SCALE, hWnd, 0);
  if (hCamera == 0)
  {
    MessageBox(hWnd, "On_Create() 의 capCreateCaptureWindowW() 에러. 캡쳐 윈도우 생성 실패.""Button", MB_OK);
    PostQuitMessage(0);
  }
  //2. 캡처 윈도우와 드라이버 연결
  bRet = capDriverConnect(hCamera, 0);
  if (bRet == FALSE)
  {
    MessageBox(hWnd, "On_Create() 의 capDriverConnect() 에러. 드라이버에 연결할 수 없습니다.""Button", MB_OK);
    PostQuitMessage(0);
  }
  //3. 캡처윈도우에 프리뷰 모드로 찍을 프레임 값 설정
  capPreviewRate(hCamera, 1);    //숫자가 작을 수록 화면에 보이는 속도가 빨라진다.
  //4. 캡처되는 이미지의 정보를 얻어냄
  capGetVideoFormat(hCamera, &stBmpInfo, sizeof(stBmpInfo));
  //5. 원하는 크기로 생성시킨 값으로 수정
  stBmpInfo.bmiHeader.biWidth = X_SCALE;
  stBmpInfo.bmiHeader.biHeight = Y_SCALE;
  //6. 콜백 함수 호환을 위한 비디오 포맷 설정
  capSetVideoFormat(hCamera, &stBmpInfo, sizeof(stBmpInfo));
  //7. 
  capSetCallbackOnFrame(hCamera, Capture);
  //8. 캡쳐 윈도우에 프리뷰 모드로 보여줌
  capPreview(hCamera, TRUE);

  return 0;
}

LRESULT On_Paint(WPARAM wParam, LPARAM lParam)
{
  HDC hdc;
  PAINTSTRUCT ps;

  hdc = BeginPaint(hWnd, &ps);
  EndPaint(hWnd, &ps);

  return 0;
}

//그림한장 호출될때마다 이 함수가 호출. 2번째 인자에 비디오 정보가 실려오기에 헤더를 분석해야한다.
//
LRESULT CALLBACK Capture(HWND hWpWnd,LPVIDEOHDR lpVHdr)
{
  HDC hdc;

  hWpWnd = hWnd;

  hdc = GetDC(hWpWnd);
  
  StretchDIBits(hdc, X_SCALE, 0, X_SCALE, Y_SCALE, 00, X_SCALE, Y_SCALE, lpVHdr->lpData, &stBmpInfo, DIB_RGB_COLORS, SRCCOPY);

  ReleaseDC(hWpWnd, hdc);

  return;
}


우선 실험적으로 저 함수를 활용하여 바로 오른쪽에 띄워봤다.

아주 잘 출력이 되었고 이쪽 함수들에 대한 나름 정리가 잘 된 자료는



이것이다. 완벽하진 않지만 나름 정리가 잘 되어 있다.


그리고 

저부분 


//hWpWnd = hWnd; 


이거 처리 해줘도 잘 되는 것으로 보아 아마 핸들러값은 건드리지 않는ㄱ ㅏ보다. 그냥 전역 꺼 가져다가 쓰자.



LRESULT CALLBACK Capture(HWND hWpWnd, LPVIDEOHDR lpVHdr)
{
  HDC hdc;
  int iCountX;
  int iCountY;

  hdc = GetDC(hWnd);

  StretchDIBits(hdc, X_SCALE, 0, X_SCALE, Y_SCALE, 00, X_SCALE, Y_SCALE, lpVHdr->lpData, &stBmpInfo, DIB_RGB_COLORS, SRCCOPY);

  for (iCountY = 0; iCountY < Y_SCALE; ++iCountY)
  {
    for (iCountX = 0; iCountX < X_SCALE; ++iCountX)
    {
      //*(lpVHdr->lpData + (iCountY * X_SCALE + iCountX) * 3 + 0) = 0;
      *(lpVHdr->lpData + (iCountY * X_SCALE + iCountX) * 3 + 1= 0;
      *(lpVHdr->lpData + (iCountY * X_SCALE + iCountX) * 3 + 2= 0;
    }
  }

  ReleaseDC(hWnd, hdc);

  return;
}


비디오 데이터의 위치가 오는데 그 위치를 수정하기 떄문에 

원본을 수정을 가했기에 원본이 변한 그림이 뜨는 것이다.

그전에 StretchDIBits 함수로 원본을 먼저 출력을 하기 때문에 

setpixel() 로 찍으면 오른쪽 이 뚝뚝 끊긴다. 원복 복사가 아닌 점으로 다 일일이 찍기 때문이다.

이제 평균값을 구한다고 하는데,





LRESULT CALLBACK Capture(HWND hWpWnd, LPVIDEOHDR lpVHdr)
{
  HDC hdc;
  int iCountX;
  int iCountY;
  WCHAR wcStr[50= { 0, };
  static BYTE vData[X_SCALE * Y_SCALE * 3];  //영상처리를 위해서 데이터 값을 그대로 가져올 것.

  hdc = GetDC(hWnd);

  memcpy(vData, lpVHdr->lpData, lpVHdr->dwBufferLength);
  for (iCountY = 0; iCountY < Y_SCALE; ++iCountY)
  {
    for (iCountX = 0; iCountX < X_SCALE; ++iCountX)
    {
      //*(vData + (iCountY * X_SCALE + iCountX) * 3 + 0) = 0;
      *(vData + (iCountY * X_SCALE + iCountX) * 3 + 1= 0;
      *(vData + (iCountY * X_SCALE + iCountX) * 3 + 2= 0;
    }
  }
  StretchDIBits(hdc, X_SCALE, 0, X_SCALE, Y_SCALE, 00, X_SCALE, Y_SCALE, vData, &stBmpInfo, DIB_RGB_COLORS, SRCCOPY);

  memcpy(vData, lpVHdr->lpData, lpVHdr->dwBufferLength);
  for (iCountY = 0; iCountY < Y_SCALE; ++iCountY)
  {
    for (iCountX = 0; iCountX < X_SCALE; ++iCountX)
    {
      *(vData + (iCountY * X_SCALE + iCountX) * 3 + 0= 0;
      //*(vData + (iCountY * X_SCALE + iCountX) * 3 + 1) = 0;
      *(vData + (iCountY * X_SCALE + iCountX) * 3 + 2= 0;
    }
  }
  StretchDIBits(hdc, 0, Y_SCALE, X_SCALE, Y_SCALE, 00, X_SCALE, Y_SCALE, vData, &stBmpInfo, DIB_RGB_COLORS, SRCCOPY);

  memcpy(vData, lpVHdr->lpData, lpVHdr->dwBufferLength);
  for (iCountY = 0; iCountY < Y_SCALE; ++iCountY)
  {
    for (iCountX = 0; iCountX < X_SCALE; ++iCountX)
    {
      *(vData + (iCountY * X_SCALE + iCountX) * 3 + 0= 0;
      *(vData + (iCountY * X_SCALE + iCountX) * 3 + 1= 0;
      //*(vData + (iCountY * X_SCALE + iCountX) * 3 + 2) = 0;
    }
  }
  StretchDIBits(hdc, X_SCALE, Y_SCALE, X_SCALE, Y_SCALE, 00, X_SCALE, Y_SCALE, vData, &stBmpInfo, DIB_RGB_COLORS, SRCCOPY);

  /*
  wsprintf(wcStr, L"%d", lpVHdr->dwBufferLength / stBmpInfo.bmiHeader.biWidth / stBmpInfo.bmiHeader.biHeight);
  SetWindowText(hWnd, wcStr);
  */

  ReleaseDC(hWnd, hdc);

  return;
}

이렇게 수정 되었으며 실행 결과,





이와 같이 원본, B, G, R 순 대로 출력됨을 확인했다.

즉, 4 사분면 위치에 출력하기 위해 순대로 보자면

  StretchDIBits(hdc, X_SCALE, 0, X_SCALE, Y_SCALE, 00, X_SCALE, Y_SCALE, vData, &stBmpInfo, DIB_RGB_COLORS, SRCCOPY);

X_SCALE 크기 만큼 이동 후 출력


  StretchDIBits(hdc, 0, Y_SCALE, X_SCALE, Y_SCALE, 00, X_SCALE, Y_SCALE, vData, &stBmpInfo, DIB_RGB_COLORS, SRCCOPY);

Y_SCALE 크기 만큼 이동 후 출력


  StretchDIBits(hdc, X_SCALE, Y_SCALE, X_SCALE, Y_SCALE, 00, X_SCALE, Y_SCALE, vData, &stBmpInfo, DIB_RGB_COLORS, SRCCOPY);

X_SCALE, Y_SCALE 크기 만큼 이동 후 출력

728x90