为什么学IO :
基本绝大多数的工业的程序中都少不了文件的读写操作,包括图像的读写,没有文件的读写操作很难对硬件资源更好的利用,同时网络的输入部分需要良好的基本功去处理图像,在数据进入CNN网络前保证输入流的正确性,学好IO模块是我们本课的重点知识之一,同时也是算法落地涉及的一个重要技术。
明白stream流概念、理解图像本质概念、学会通过指针进行图像的获取修改、学会其他文件输入输出
IO文件读取
- 二进制文件读写
- 二进制图像的读取
- 高位图转8位图
- stream流
- .txt 文件读写
- json文件读取
- log 日志读写
- IO多路机制
图像编码
RGB
图像的一种编码方式,是我们通常深度学习网络的输入模式 8bit
的rgb图, 所以在应用上需要将其他输入不管是什么模型转换到8bit的rbg格式。
- .bmp:标准图像最原始的位图
- .png:进行了无损压缩,图像质量保存较为整
- .jpg:文件最为常用,是经过压缩的图像,最适合家用储存等
一个100x100单通道的图像保存为二进制文件.bin后的大小为多少?
100x100x8= 80000bit 10000字节 1K = 1024字节 10000/1024 = 9.765625KB
YUV
YUV也是图像的一种编码模式,为了编码、传输的方便,它的储存空间占比相比于 rbg图来说更小,减少了带宽占用和信息出错,每个色度信息对应多个亮度信息,最早的设计是为了兼容彩色电视与黑白电视的兼容问题。
Y分量中存储的是亮度信息,就是没有uv单纯的y分量的输出就是我们所能见黑白图 (16~235)
UV分量中存储的是色度信息 cb cr (16~240)
四个像素采样中
YUV420 : 每4个Y分量都有一个UV分量 (常用)
YUV422 : 每2个Y分量都有一个UV分量
YUV444 :每个Y分量都有一个UV分量
384 = 96 byte (字节)
提醒:嵌入式推流的时候,我们要转换格式的时候,一定要知道内存中yuv的数据流的排列方式。
图像在传输过程中,数据量较大,往往都是通过一些协议进行图像的压缩,这些压缩是有损的。
Mat
opencv中 Mat
这个结构是替代原有的 IplImage
类,它是基于opencv框架开发,它内部使用智能指针进行管理,合理的进行内存申请释放,务必学会基本使用。
type: CV_8UC1
、 CV_8UC3
、 CV_16SC1
、 CV_16SC3
、 CV_64FC3
、 CV_64FC3
- 一般的图像文件格式使用的是
Unsigned char
8bits(1字节),CvMat矩阵对应的参数类型就是
CV_8UC1
, CV_8UC2
, CV_8UC3
。(最后的1、2、3表示通道数,譬如RGB3通道就用CV_8UC3)
- 而float 是32位的,对应CvMat数据结构参数就是:
CV_32FC1
,CV_32FC2
,CV_32FC3
等 - double是64bits,对应CvMat数据结构参数:
CV_64FC1
,CV_64FC2
,CV_64FC3
等。 ```cpp // Mat构建:
cv::Mat(int rows, int cols, int type, const Scalar&s) cv::Mat(cv::Size(x, y), int type) cv::Mat(int rows, int cols, int type, void* p)
// frame = cv2.imread(“xxx.jpg”) (python)
```cpp
// Mat的指针遍历:
cv::Mat frame;
frame.at<uchar>(i, j) = 255;
frame.at<cv::Vec3b>(i, j) = cv::Vec3b(255, 255, 255); //i是行数 y j是列数x
frame.ptr<uchar>(0) = 1; // 第0行第0个元素
frame.ptr<uchar>(1) = 2; // 第1行第0个元素
frame.ptr<uchar>(0)[1]; // 第0行第1个元素
frame.ptr<cv::Vec3b>(3); // 第3行的第一个点的rgb
ptr
要比 at
快。
//Mat的指针遍历:
// CV_8UC1: 灰度图像
uchar* ptr = image.ptr<uchar>(row_index);
// CV_8UC3: 彩色图像
Vec3b* ptr = image.ptr<cv::Vec3b>(row_index);
// CV_32FC1: 单通道浮点数图像
float* ptr = image.ptr<float>(row_index);
// CV_32FC3: 三通道浮点数图像
Vec3f* ptr = image.ptr<cv::Vec3f>(row_index);
OpenCV Mat数据类型指针ptr的使用
cv::Mat image = cv::Mat(400, 600, CV_8UC1); //宽400,长600
uchar * data00 = image.ptr(0);
uchar * data10 = image.ptr(1);
uchar * data01 = image.ptr(0)[1];
- 定义了一个Mat数组对象
image
。 data00
是指向image第一行第一个元素的指针。data10
是指向image第二行第一个元素的指针。data01
是指向image第一行第二个元素的指针。.
注意:
如果你的程序使用来 image.ptr
指针,并且出现了下面这样的错误:(假设你使用的软件是Visual Studio 201x)
某某.exe中的 0x75065b68 处有未经处理的异常:Microsoft C++ 异常; 内存位置0x85e790处的cv::Exception。
这可能是因为你不理解 image.ptr
这个指针,犯了这样的错误: image.ptr(1)
指的不是image中第二个像素,而是第二行第一个像素的指针。
使用上面的代码举例:image有400行,有400600个像素。假设现在你想得到第3行第42个像素的指针,如果你写成:`uchar data = image.ptr
这样写是错误的,会出现上面的错误。你得到的不是第3行第42个像素的指针,而是第 (3×image.cols + 41)
行第0个像素的指针,因为没有 (3×image.cols + 41)
行,所以没有这个指针,所以错误。
正确的写法:uchar * data = image.ptr(2)[41];
所以要注意这一点:如果程序可以正常编译,但是运行时出错,很有可能是你给指针赋值的时候,索引值溢出指定范围,指针乱指,导致程序跑偏,所以只有在运行时才能发现错误。
cv::Mat image = cv::Mat(400, 600, CV_8UC3); //宽400,长600,3通道彩色图片
uchar * data000 = image.ptr<uchar>(0);
uchar * data100 = image.ptr<uchar>(1);
uchar * data001 = image.ptr<uchar>(0)[1];
uchar * data
cv::Mat image = cv::Mat(400, 600, CV_8UC3); //宽400,长600,3通道彩色图片
cv::Vec3b * data000 = image.ptrcv::Vec3b(0);
cv::Vec3b * data100 = image.ptrcv::Vec3b(1);
cv::Vec3b * data001 = image.ptrcv::Vec3b(0)[1];
cv::Vec3b * data
Stream流
C++中将信息的流动抽象为流,将数据从一个对象到另一个对象的流动对象称为流。
流在使用前需要被建立,使用后需要删除。信息的流动都被抽象为流,包括我们的外部设备的输入。
C++语言不直接处理输入输出,而是通过一簇定义在标准库中的类型来处理IO。这些类型支持从设备读取数据、向设备写入数据的IO操作,设备可以是文件、控制台窗口等。还有一些类型允许内存IO,即,从string读取数据,向string写入数据。流概念是IO文件操作的基石
C++/C++11中头文件 <iostream>
定义了标准输入/输出流对象。包含了<iostream>
也自动包含了 <ios>
、 <streambuf>
、 <istream>
、 <ostream>
和 <iosfwd>
,所以程序开头总要有个 #include <iostream>
。
Stream流操作步骤:
1. 程序创建一个流对象
2. 指定流与文件的关系
3. 程序操纵流对象
4. 通过流对象再对文件进行相应的操作
5. 删除已经使用过的流
istream
:(输入流)类型,提供输入操作;ostream
:(输出流)类型,提供输出操作;cin
:一个 istream
对象,标准输入流,用来从标准输入读取数据;(全局变量)cout
:一个 ostream
对象,标准输出流,从标准输出写入数据,(全局变量)>> 运算符
:用来从一个 istream对象
读取输入数据;标准输出运算符 流插入运算符 << 运算符
:用来向一个 ostream对象
写入输出数据;标准输入提取符 流提取运算符getline函数
:从一个给定的 istream
读取一行数据,存入一个给定的string对象中。
steam流
的状态成员函数:
- good() 若stream正常无误则返回true(表示goodbit成立)
- eof() 若遇到end-of-file则返回true(表eofbit成立)
- fail() 若发生错误则返回true(表failbit||badbit成立)
- bad() 若发生毁灭性错误则返回true(表badbit成立)
- rdstate() 返回当前stream已设立的所有标志
- clear() 返回当前stream所有标志
- clear(state) 返回当前stream所有标志后,设立state
- setstate(state) 追加标志state
流常用成员函数:open
打开 <br />
put把一个字符写到输出流中<br />
write内存中的一块内容写道输出流<br />
seekp移动文件内部的写址<br />
tellp返回文件内当前文件流的指针<br />
close关闭<br />
getline函数` ,从一个给定的istream读取一行数据,存入一个给定的string对象中
C语言里操作文件的操作是通过文件指针及一些函数,c++中文件操通过fstream文件流实现。fstream类主要分成 ifstream
、 ofstream
、 fstream
这3个类来实现c++文件的读取。
同时C语言的 fopen()
File类 依然可以使用,通过 fopen()
实现指针指向文件流进行处理。
C方式文件读取IO
c语言实现文件写入
FILE *fopen(const char *filename, const char *mode); // 构建File类;
demo:
#include<stdio.h>
FILE *fp = fopen("demo.txt", "w");
fprintf(fp, "%s %d", "Hello_KK", 2021);
fclose(fp);
File
类包含的函数:
fopen() 打开流
fclose() 关闭流
fputc() 写一个字符到流中
fgetc() 从流中读一个字符
fseek() 在流中定位到指定的字符
fputs() 写字符串到流
fgets() 从流中读一行或指定个字符
fprintf() 按格式输出到流
fscanf() 从流中按格式读取
feof() 到达文件尾时返回真值
ferror() 发生错误时返回其值
rewind() 复位文件定位器到文件开始处
remove() 删除文件
fread() 从流中读指定个数的字符
fwrite() 向流中写指定个数的字符
tmpfile() 生成一个临时文件流
tmpnam() 生成一个唯一的文件名
c语言实现文件读取
int fgetc(FILE *stream);
:从指定流stream获取下一个字符,并指针后移,若到达文件末尾或发生读写错误返回EOF;if(feof(FILE *stream));
:判断指针是否指向EOF; ```cpp // read: FILE *fp = fopen(“xx.txt”, “r”); while(1){ c = fgetc(fp); if(feof(fp)) break; } fclose(fp);
// write:
include
FILE *fp = fopen(“demo.txt”, “w”); fprintf(fp, “%s %d”, “Hello_KK”, 2021); fclose(fp);
<a name="3JciK"></a>
## C++方式文件读取IO:
```cpp
#include<fstream>
(fstream.h)
ofstream out("...", ios::out);
ifstream in("...", ios::in);
out<< “xxx”;
out.close
int asd;
in>> asd;
输入模式:
ios::in
:文件以输入方式打开(文件数据输入到内存)硬盘 -> 内存ios::out
:文件以输出方式打开(内存数据输出到文件)内存 -> 硬盘ios::app
:以追加的方式打开文件ios::ate
:文件打开后定位到文件尾ios::binary
:以二进制方式打开文件ios::nocreate
:不建立文件,所以文件不存在时打开失败ios::noreplace
:不覆盖文件,打开文件如果文件存在失败ios::trunc
:如果文件存在,把文件长度设为0
fstream
继承流常用成员函数:open
:打开put
:把一个字符写到输出流中write
:内存中的一块内容写道输出流seekp
:移动文件内部的写址 tellp
:返回文件内当前文件流的指针close
:关闭 getline函数
:从一个给定的istream读取一行数据,存入一个给定的string对象中
binary二进制
为什么要二进制读写?
高效 省空间 not文本 嵌入式应用
打开方式:
ofstream outFile("xxx.dat", ios::out | ios::binary);
ifstream inFile("xxx.dat", ios::in|ios::binary);
读写方式:
outFile.write((char*)&s, sizeof(s));
outFile.read((char*)&s, sizeof(s));
json
json格式比txt更加的轻易,适合很多场景下使用,比xml占用内存更小,json对接其他语言数据库等也更加的方便灵活。
// Json一般格式:
{
"name" : "xiaoliu",
"age" : "21"
}
关于int8、int16、int64的关系
有符号整形范围:int8
:8位整数(8bit integer), 占1个字节,取值范围:[-128: 127]
Int16
:16位整数(16bit integer),相当于short 占2个字节,取值范围:[-32768:32767]
Int32
:32位整数(32bit integer), 相当于 int 占4个字节,取值范围: [-2147483648:2147483647]
Int64
:64位整数(64bit interger), 相当于 long long 占8个字节 ,取值范围:[-9223372036854775808:9223372036854775807]
字 word
字节 byte
位 bit
,来自英文bit,音译为“比特”,表示二进制位。
字长是指字的长度
- 1字=2字节(1 word = 2 byte)
1字节=8位(1 byte = 8bit)
一个字的字长为16,一个字节的字长是8
固定长度的整型,包括有符号整型或无符号整型。
无符号整型范围
- UInt8-[0:255]
- UInt16-[0:65535]
- UInt32-[0:4294967295]
- UInt64-[0:18446744073709551615]
参考链接:https://blog.csdn.net/WU9797/article/details/81331281
数据类型转换
sizeof()
高级语言一般都以字节位单位进行运算,机器语言、汇编语言以位为处理单元,c++语言继承了c语言的特性,同时可以进行位操作, c++里面好多的类型都是不知道字节大小的,int、double等都是根据不同的操作系统定义的,那么你做后续操作的时候就会涉及到底层的位处理时,操作不当就会引发混乱导致程序崩溃。
sizeof是运算符 不是函数。
自动类型转换:
int a= 123.934; // a -> int
float b =12.12; // b -> float
auto c = a*b; // c -> float
强制类型转换:
double number = 3.7;
int val;
val = static_cast<int>(number);
val = int(number); C++
val = (int) number; C
自定义类型
typedef char ubuntu_char
变量修饰符
const static
long double |
---|
double |
float |
unsigned long long int |
long long int |
unsigned long int |
long int |
unsigned int |
int |
存储空间
对于 char
类型转 int
、 float
时,如果发现最高位位1,则全部扩充1,这会导致你的输出结果不一致。
unsigned char
是无符号的char类型,这个再扩充时会填充0,这也是为什么使用unsigned char这个类型而不用char