해당 포스트는 "열혈강의 영상처리 프로그래밍" 책의 내용을 요약한 것이다.
※ 비트맵 파일 읽기와 쓰기
비트맵 파일의 읽기와 쓰기에서 주의할 점은 비트맵 파일 안에서 영상은 상하가 반전되어 저장된다. 위 그림에서와 같이 프로그램 내부에서는 왼쪽 상단부터 시작해서 행 우선 배열에 따라 픽셀의 배열이 이루어지나 BMP 파일 안에서는 영상의 왼쪽 하단부터 시작해서 행 우선 배열에 따라 저장되 상하 좌표가 바뀐다.
ex) 영상 데이터 클래스 구현하기 포스트 코드에서 나왔던 LoadImage() 와 SaveImage()를 구현할 것이다.
bool LoadImage(const char* filename) { assert(sizeof(T) == 1); // 일반적인 비트맵 파일은 1byte이기 때문에 BYTE형의 영상인 경우에만 동작할 수 있도록 한다. if (strcmp(".BMP", &filename[strlen(filename) - 4])) //확장자가 BMP인 비트맵 파일이 맞는 지 확인 { FILE* pFile = NULL; fopen_s(&pFile, filename, "rb"); // 바이너리 읽기 모드 if (!pFile) return false; BITMAPFILEHEADER fileHeader; if (!fread(&fileHeader, sizeof(BITMAPFILEHEADER), 1, pFile)) { fclose(pFile); return false; } if (fileHeader.bfType != 0x4D42) // 'BM' 문자 검사 { fclose(pFile); return false; } BITMAPINFOHEADER infoHeader; if (!fread(&infoHeader, sizeof(BITMAPINFOHEADER), 1, pFile)) { fclose(pFile); return false; } if (infoHeader.biBitCount != 8 && infoHeader.biBitCount != 24) //회색조 영상 또는 트루 컬러영상만을 읽을 수 있도록 한다. { fclose(pFile); return false; } if (m_nWidth != infoHeader.biWidth && m_nHeight != infoHeader.biHeight && m_nChannels != infoHeader.biBitCount / 8) { if (m_pImageData) delete[] m_pImageData; m_nChannels = infoHeader.biBitCount / 8; m_nHeight = infoHeader.biHeight; m_nWidth = infoHeader.biWidth; m_nWStep = (m_nWidth*m_nChannels * sizeof(T) + 3)&~3; m_pImageData = new T[m_nHeight*m_nWStep]; } fseek(pFile, fileHeader.bfOffBits, SEEK_SET); //실제 픽셀 데이터 위치를 가리킨다. int r; for (r = m_nHeight - 1; r >= 0; r--) //비트맵 파일은 상하가 바뀌므로 m_nHeight-1부터 접근 { if (!fread(&m_pImageData[r*m_nWStep], sizeof(BYTE), m_nWStep, pFile)) { fclose(pFile); return false; } } fclose(pFile); return true; } else { return false; } }
비트맵 파일의 헤더정보를 읽는 과정과 저장할 픽셀 데이터에 관한 영상 정보를 읽는 과정, 픽셀 데이터를 읽는 과정으로 구성된다.
bool SaveImage(const char* filename) { assert(sizeof(T) == 1); // 일반적인 비트맵 파일은 1byte이기 때문에 BYTE형의 영상인 경우에만 동작할 수 있도록 한다. if (strcmp(".BMP", &filename[strlen(filename) - 4])) { FILE* pFile = NULL; fopen_s(&pFile, filename, "wb"); if (!pFile) return false; BITMAPFILEHEADER fileHeader; fileHeader.bfType = 0x4D42; // 'BM' //(1) fileHeader.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + m_nWStep*m_nHeight + (m_nChannels == 1) * 1024; fileHeader.bfReserved1 = 0; fileHeader.bfReserved2 = 0; //(2) fileHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + (m_nChannels == 1) * 256 * sizeof(RGBQUAD); fwrite(&fileHeader, sizeof(BITMAPFILEHEADER), 1, pFile); BITMAPINFOHEADER infoHeader; infoHeader.biSize = sizeof(BITMAPINFOHEADER); infoHeader.biWidth = m_nWidth; infoHeader.biHeight = m_nHeight; infoHeader.biPlanes = 1; infoHeader.biBitCount = m_nChannels * 8; infoHeader.biCompression = BI_RGB; infoHeader.biSizeImage = m_nWStep*m_nHeight; infoHeader.biClrImportant = 0; infoHeader.biClrUsed = 0; infoHeader.biXPelsPerMeter = 0; infoHeader.biYPelsPerMeter = 0; fwrite(&infoHeader, sizeof(BITMAPINFOHEADER), 1, pFile); if (m_nChannels == 1) //(3) { for (int l = 0; l<256; l++) { RGBQUAD GrayPalette = { l, l, l, 0 }; fwrite(&GrayPalette, sizeof(RGBQUAD), 1, pFile); } } int r; for (r = m_nHeight - 1; r >= 0; r--) { fwrite(&m_pImageData[r*m_nWStep], sizeof(BYTE), m_nWStep, pFile); } fclose(pFile); return true; } else { return false; } }
SaveImag(0 함수와 반대 과정으로 비트맵 파일 헤더와 영상 정보 헤더를 생성하고 파일에 쓰는 과정, 픽셀 데이터를 파일에 쓰는 과정으로 구성된다. 회색조 영상은 팔레트 정보를 기록하는 과정도 있다. 또한, BMP파일에 픽셀 데이터를 쓸 때 상하 반대로 해서 써야한다.
(1)번 코드 fileHeader.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + m_nWStep*m_nHeight + (m_nChannels == 1) * 1024; 는 비트맵 파일의 크기를 구하는 것으로 m_nChannels가 1이면 즉, 회색조 영상일 경우 팔레트 정보의 크기를 추가해 주는 코드이다. 1024 대신 (2)번 코드와 같이 256*sizeof(RGBQUAD)를 곱해줘도 된다.
(3)번 코드는 회색조 영상의 경우 팔레트 정보를 추가해 줘야 하기 때문에 구현한 코드이다.
'영상처리 프로그래밍' 카테고리의 다른 글
영상 입출력 프로그램 만들기(4) - 대화상자기반 영상 출력 (0) | 2017.06.09 |
---|---|
영상 입출력 프로그램 만들기(3) - MDI에서 화면 출력 (0) | 2017.06.07 |
영상 입출력 프로그램 만들기(1) - 비트맵 구조 (0) | 2017.06.06 |
영상 데이터 클래스 구현(5) (0) | 2017.06.04 |
영상 데이터 클래스 구현(4) (0) | 2017.06.04 |