Mnist 데이터 파일 형식
Mnist 손글씨 데이터셋 공식 데이터 베이스 :
yann.lecun.com/exdb/mnist/index.html
위 문서를 내리다 보면,
파일 형식 부분이 나옵니다.
내용을 표로 정리하면
훈련 레이블 파일 | 32bit(=4byte) | + 32bit(=4byte) | + 8bit(=1byte) * 이미지 수 | |
안에 들어있는 데이터 | 매직 넘버 | 이미지 수 | 레이블(정답 데이터) |
훈련 세트 파일 | 32bit(=4byte) | +32bit(=4byte) | +32bit(=4byte) | +32bit(=4byte) | + 8bit(=1byte) * 행*열*이미지 수 |
들어있는 데이터 | 매직 넘버 | 이미지 수 | 이미지 픽셀 행 | 이미지 픽셀 열 | 픽셀 |
이렇게 됩니다.
문서의 내용은 이렇습니다.
훈련 세트 레이블 파일 (train-labels-idx1-ubyte) :[오프셋] [유형] [값] [설명] 레이블 값은 0-9입니다. |
훈련 세트 이미지 파일 (train-images-idx3-ubyte) :[오프셋] [종류] [값] [설명]0000 32 비트 정수 0x00000803 (2051) 매직 넘버 0004 32 비트 정수 60000 이미지 수 0008 32 비트 정수 28 행 수 0012 32 비트 정수 28 열 수 0016 unsigned byte ?? 픽셀 0017 unsigned byte ?? 픽셀 ........ xxxx unsigned byte ?? 픽셀 픽셀은 행 단위로 구성됩니다. 픽셀 값은 0 ~ 255입니다. 0은 배경 (흰색), 255는 전경 (검정)을 의미합니다. |
파일에서 매직 넘버 데이터 파싱(parsing)
먼저 매직 넘버를 읽겠습니다.
마찬가지로 내용을 표로 정리하면 이렇습니다.
총합 4byte(32bit) | 첫번째 byte(=8bit) | 두번째 byte(=8bit) | 세번째 byte(=8bit) | 네번째 byte(=8bit) |
매직 넘버 | 0 (공백) | 0 | 데이터 유형(16진수) | 차원 수 |
문서의 내용은 이렇습니다.
IDX 파일 형식IDX 파일 형식은 다양한 숫자 유형의 벡터 및 다차원 행렬에 대한 간단한 형식입니다.기본 형식은 매직 넘버는 정수 (MSB 먼저)입니다. 처음 2 바이트는 항상 0입니다. 세 번째 바이트는 데이터 유형을 코드합니다. 4 번째 바이트는 벡터 / 행렬의 차원 수를 코딩합니다. 벡터는 1, 행렬은 2입니다. 각 차원의 크기는 4 바이트 정수입니다. (대부분의 비 Intel 프로세서에서와 같이 MSB 우선, 하이 엔디안). 데이터는 C 배열처럼 저장됩니다. 즉, 마지막 차원의 인덱스가 가장 빠르게 변경됩니다. |
엔디안이란?
MNIST 데이터베이스의 파일 형식.... Intel 프로세서 및 기타 로우 엔디안 시스템 사용자는 헤더의 바이트를 뒤집어야 합니다. |
저는 Intel CPU를 쓰고 있습니다.
빅 엔디언 CPU는 32bit integer(정수)를 받았을 때
8bit(=1byte) 씩 앞에서부터 메모리에 저장하는데,
Intel 제조사 같은리틀 엔디언 방식 CPU는 뒤에서부터 메모리에 저장합니다.
(각 방식의 장단점은 위키피디아,
자신의 CPU 엔디안 확인법 등은 www.joinc.co.kr/w/Site/Network_Programing/Documents/endian 에 잘 나와있습니다.)
매직 넘버 등 32비트 integer 형을 읽을 때 byte 단위로 거꾸로 재배치해야 합니다.
리틀 엔디안 CPU에서 32bit 정수형을 읽기 위해 재배치
int main(){
//시프트 연산 테스트
std::cout << "10진수 2144444444 = 2진수로 "<<std::bitset<32>(2144444444) << std::endl;
std::cout << "10진수 2144444444를 ReverseInt 2진수로 " <<std::bitset<32>(op.ReverseInt(2144444444)) << std::endl;
return 0;
}
//1바이트(8비트)씩 거꾸로 배열한 int를 반환
int OpencvPractice::ReverseInt(int i)
{
unsigned char ch1, ch2, ch3, ch4;
ch1 = i & 255;
ch2 = (i >> 8) & 255; //i를 8비트만큼 시프트연산으로 버리고, 8비트만큼 AND연산으로 읽음
ch3 = (i >> 16) & 255;
ch4 = (i >> 24) & 255;
return((int)ch1 << 24) + ((int)ch2 << 16) + ((int)ch3 << 8) + ch4;
}
32bit integer인 i를 2,144,444,444라고 가정해보고 실제로 연산해보면
8bit(=1byte)씩 거꾸로 배열되었다는 것을 알 수 있습니다.
이미지 수, 이미지 행 열 정보, 픽셀 데이터 파싱
이미지 수와 행 열 정보도 매직 넘버를 ReverseInt로 파싱 했듯, 똑같이 ReverseInt로 파싱 합니다.
픽셀 데이터는 unsigned byte (=1byte)이기 때문에 unsigned char (=1byte)로 파싱 합니다.
전체 코드
필요한 헤더 파일 :
#include "opencv2/opencv.hpp"
#include <vector>
#include <iostream>
#include <fstream>
메인 함수
int main() {
OpencvTutorial op;
std::cout << "Hello OpenCV" << CV_VERSION << std::endl;
//read MNIST iamge into OpenCV Mat vector
std::vector<cv::Mat> trainingVec;
std::vector<uchar> labelVec;
op.MnistTrainingDataRead("Resources/train-images.idx3-ubyte", trainingVec, 10);
op.MnistLabelDataRead("Resources/train-labels.idx1-ubyte", labelVec, 10);
op.MatPrint(trainingVec, labelVec);
return 0;
}
Mnist 데이터 읽어오기, 출력 함수들
//인자로 받은 vector에 Mnist 훈련 데이터를 파싱해 Matrix으로 저장
void OpencvPractice::MnistTrainingDataRead(std::string filePath, std::vector<cv::Mat>& vec, int readDataNum)
{
std::ifstream file(filePath, std::ios::binary);
if (file.is_open())
{
int magic_number = 0;
int number_of_images = 0;
int n_rows = 0;
int n_cols = 0;
//ifstream::read(str, count)로 count만큼 읽어 str에 저장
//char은 1바이트, int는 4바이트이므로 int 1개당 char 4개의 정보만큼 가져옴
file.read((char*)&magic_number, sizeof(magic_number));
magic_number = ReverseInt(magic_number);
file.read((char*)&number_of_images, sizeof(number_of_images));
number_of_images = ReverseInt(number_of_images);
file.read((char*)&n_rows, sizeof(n_rows));
n_rows = ReverseInt(n_rows);
file.read((char*)&n_cols, sizeof(n_cols));
n_cols = ReverseInt(n_cols);
if (readDataNum > number_of_images || readDataNum <= 0)
readDataNum = number_of_images;
for (int i = 0; i < readDataNum; ++i)
{
cv::Mat tp = cv::Mat::zeros(n_rows, n_cols, ConvertCVGrayImageType(magic_number));
for (int r = 0; r < n_rows; ++r)
{
for (int c = 0; c < n_cols; ++c)
{
//magicnumber에서 얻은 타입 정보가 unsigned byte 일 경우
if (ConvertCVGrayImageType(magic_number) == CV_8UC1) {
unsigned char temp = 0;
file.read((char*)&temp, sizeof(temp));
tp.at<uchar>(r, c) = (int)temp;
}
}
}
vec.push_back(tp);
}
}
}
void OpencvPractice::MnistLabelDataRead(std::string filePath, std::vector<uint8_t>& vec, int readDataNum)
{
std::ifstream file(filePath, std::ios::binary);
if (file.is_open())
{
int magic_number = 0;
int number_of_images = 0;
//ifstream::read(str, count)로 count만큼 읽어 str에 저장
//char은 1바이트, int는 4바이트이므로 int 1개당 char 4개의 정보만큼 가져옴
file.read((char*)&magic_number, sizeof(magic_number));
magic_number = ReverseInt(magic_number);
file.read((char*)&number_of_images, sizeof(number_of_images));
number_of_images = ReverseInt(number_of_images);
if (readDataNum > number_of_images || readDataNum <= 0)
readDataNum = number_of_images;
for (int i = 0; i < readDataNum; ++i)
{
//magicnumber에서 얻은 타입 정보가 unsigned byte 일 경우
if (ConvertCVGrayImageType(magic_number) == CV_8UC1) {
uint8_t temp = 0;
file.read((char*)&temp, sizeof(temp));
vec.push_back(temp);
}
}
}
}
int OpencvPractice::ConvertCVGrayImageType(int magicNumber)
{
magicNumber = (magicNumber >> 8) & 255; //3번째 바이트(픽셀 타입)만 가져오기
//리틀 엔디안 CPU에서 magicNumber = ((char*)&magicNumber)[1];와 같음
//빅 엔디안 CPU에서 magicNumber = ((char*)&magicNumber)[2];와 같음
switch (magicNumber) {
case 0x08: return CV_8UC1;//unsigned byte, 흑백 채널 단일
case 0x09: return CV_8SC1;//signed byte, 흑백 채널 단일
case 0x0B: return CV_16SC1;//short(2 바이트), 흑백 채널 단일
case 0x0C: return CV_32SC1;//int(4 바이트), 흑백 채널 단일
case 0x0D: return CV_32FC1;//float(4 바이트), 흑백 채널 단일
case 0x0E: return CV_64FC1;//double(8 바이트), 흑백 채널 단일
default: return CV_8UC1;
}
}
void OpencvPractice::MatPrint(std::vector<cv::Mat>& trainingVec, std::vector<cv::uint8_t>& labelVec)
{
std::cout << "읽어온 훈련 데이터 수 : " << trainingVec.size() << std::endl;
std::cout << "읽어온 정답 데이터 수 : " << labelVec.size() << std::endl;
cv::namedWindow("Window", cv::WINDOW_AUTOSIZE);
for (int i = 0; i < labelVec.size() && i < trainingVec.size(); i++) {
imshow("Window", trainingVec[i]);
std::cout << i << "번째 이미지 정답 : " << (int)labelVec[i] <<std::endl;
//아무 키나 누르면 다음
if (cv::waitKey(0) != -1)
continue;
}
}
출처 :
Mnist 데이터셋 도움됐던 설명글 (ubyte 파일 읽기)
givemesource.tistory.com/1?category=681642
blog.naver.com/acwboy/220584307823
2진수 출력
blog.naver.com/herbbread/220817372685
프로세서에 따라 다른 비트 수
blog.naver.com/eslectures/80143511938
손글씨 숫자 인식할 때
스마트폰과 로컬 호스트끼리 연결해서 해보고 싶네요.
통신할 때도 엔디안 나오던데.. 아무튼 서버 쪽 공부해야 할 듯
'프로그래밍 > Opencv' 카테고리의 다른 글
비주얼 스튜디오 c++ 공유 라이브러리[dll,lib] 쓰기 위한 최소 설정 (2) | 2022.10.05 |
---|