为什么学IO :

基本绝大多数的工业的程序中都少不了文件的读写操作,包括图像的读写,没有文件的读写操作很难对硬件资源更好的利用,同时网络的输入部分需要良好的基本功去处理图像,在数据进入CNN网络前保证输入流的正确性,学好IO模块是我们本课的重点知识之一,同时也是算法落地涉及的一个重要技术。

明白stream流概念、理解图像本质概念、学会通过指针进行图像的获取修改、学会其他文件输入输出

IO文件读取

  • 二进制文件读写
    • 二进制图像的读取
    • 高位图转8位图
    • stream流
  • .txt 文件读写
  • json文件读取
  • log 日志读写
  • IO多路机制

image.png

图像编码

RGB

图像的一种编码方式,是我们通常深度学习网络的输入模式 8bit 的rgb图, 所以在应用上需要将其他输入不管是什么模型转换到8bit的rbg格式。

  • .bmp:标准图像最原始的位图
  • .png:进行了无损压缩,图像质量保存较为整
  • .jpg:文件最为常用,是经过压缩的图像,最适合家用储存等

一个100x100单通道的图像保存为二进制文件.bin后的大小为多少?
100x100x8= 80000bit 10000字节 1K = 1024字节 10000/1024 = 9.765625KB
image.png

YUV

YUV也是图像的一种编码模式,为了编码、传输的方便,它的储存空间占比相比于 rbg图来说更小,减少了带宽占用和信息出错,每个色度信息对应多个亮度信息,最早的设计是为了兼容彩色电视与黑白电视的兼容问题。

Y分量中存储的是亮度信息,就是没有uv单纯的y分量的输出就是我们所能见黑白图 (16~235)
UV分量中存储的是色度信息 cb cr (16~240)
image.png
四个像素采样中
YUV420 : 每4个Y分量都有一个UV分量 (常用)
image.png
YUV422 : 每2个Y分量都有一个UV分量
YUV444 :每个Y分量都有一个UV分量
image.png
384 = 96 byte (字节)
提醒:嵌入式推流的时候,我们要转换格式的时候,一定要知道内存中yuv的数据流的排列方式。

图像在传输过程中,数据量较大,往往都是通过一些协议进行图像的压缩,这些压缩是有损的。

Mat

opencv中 Mat 这个结构是替代原有的 IplImage 类,它是基于opencv框架开发,它内部使用智能指针进行管理,合理的进行内存申请释放,务必学会基本使用。
type: CV_8UC1CV_8UC3CV_16SC1CV_16SC3CV_64FC3CV_64FC3

  • 一般的图像文件格式使用的是 Unsigned char 8bits(1字节),CvMat矩阵对应的参数类型就是

CV_8UC1CV_8UC2CV_8UC3 。(最后的1、2、3表示通道数,譬如RGB3通道就用CV_8UC3)

  • 而float 是32位的,对应CvMat数据结构参数就是: CV_32FC1CV_32FC2CV_32FC3
  • double是64bits,对应CvMat数据结构参数: CV_64FC1CV_64FC2CV_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)

  1. ```cpp
  2. // Mat的指针遍历:
  3. cv::Mat frame;
  4. frame.at<uchar>(i, j) = 255;
  5. frame.at<cv::Vec3b>(i, j) = cv::Vec3b(255, 255, 255); //i是行数 y j是列数x
  6. frame.ptr<uchar>(0) = 1; // 第0行第0个元素
  7. frame.ptr<uchar>(1) = 2; // 第1行第0个元素
  8. frame.ptr<uchar>(0)[1]; // 第0行第1个元素
  9. 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*image.cols + 41);`

这样写是错误的,会出现上面的错误。你得到的不是第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文件操作的基石
image.png
image.png
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类主要分成 ifstreamofstreamfstream 这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);

image.png
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
    image.png
    固定长度的整型,包括有符号整型或无符号整型。

无符号整型范围

  • 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

存储空间

image.png
对于 char 类型转 intfloat 时,如果发现最高位位1,则全部扩充1,这会导致你的输出结果不一致。

unsigned char无符号的char类型,这个再扩充时会填充0,这也是为什么使用unsigned char这个类型而不用char
image.png

逻辑语法

image.png

滤波操作

中值滤波

image.png
image.png

ARM均值滤波优化

  1. 减少内存访问次数
  2. 减少重复计算
  3. 多线程

    1. 减少内存访问次数

    image.png
    如果想对97这个点的周围做均值滤波,必须向前寻址,找到 3、0、2、6的地址,也必须向后寻址,找到4、19、3、10的地址