# Linux 与 Git(Github篇)
本人使用的是 Ubuntu 系统,在其他 Linux 系统下安装与使用 Git 方法基本一致
## 安装配置 SSH
### 安装 SSH
安装命令:
```shell
sudo apt-get install openssh-server openssh-client
```
启动 ssh 服务:
```shell
sudo /etc/init.d/ssh restart
```
安装并成功重启 ssh 服务如下:

### 配置 SSH
#### 配置 SSH 公钥
生成本地 ssh 公钥:
```shell
sudo ssh-keygen -C '邮箱地址' -t rsa
# 这条命令会在 ~/.ssh/ 下生成密钥文件
```
**注意要记住输入的密码**

测试畅通性:
```shell
ssh -v [email protected]
```
测试畅通:

## 安装 Git
```shell
sudo apt-get install git # 安装 Git
sudo apt-get install git-core # 安装 Git 服务器
```
## 配置 Github
配置 Github 个人信息总共只有两条命令,一条用于绑定用户名,另一条用于绑定邮箱地址
```shell
git config --global user.name "user_name"
git config --global user.email "email_id"
# user_name 与 email_id 用自己的用户名和邮箱地址替换即可
# 注意,这里的 email_id 要用注册 Github 时绑定的邮箱地址
```
## 创建本地仓库
在你的系统上创建一个目录。它将会被作为本地仓库使用,稍后它会被推送到 GitHub 的远程仓库。请使用如下命令:
```shell
git init FPGA--Develop-Diary
```
如果目录被成功创建,你会看到如下信息:
```shell
Initialized empty Git repository in /home/ppqppl/文档/Mytest/.git/
```
这行信息可能随你的系统不同而变化。
如图:

这里,FPGA--Develop-Dialog 是创建的目录,而 init 将其转化为一个 GitHub 仓库。
进一步要转到我们创建的文件夹下
### 文件创建
一般,对于代码仓库常创建一个 README 文件,包含代码的存储目录,使用方式等(也可以没有)
创建命令:
```shell
sudo gedit README
```
当我们的仓库下有了一定的文件之后,使用 Git 的 add 命令将文件加入索引
```shell
sudo git add README
……
#或者直接一次性添加所有文件
sudo git ./
```
git 的 add 命令可以将人一的文件和目录(文件夹)加入到索引,这里所说的“索引”,实际上是指一个有一定空间的缓存区,这个缓存区存储了所有已经被加入到 Git 仓库的文件或目录。
### 将所作改动加入索引
所有文件添加与修改好之后,就可以进行提交。意味着已经最终确定了所有文件,不再改动,已经准备好上传到自己的仓库,使用如下命令:
```shell
git commit -m "message"
# message 可以是一些信息,类似:第一次提交,修改 xxx 文件等,用于表示版本更新的内容
```
## 创建 Github 仓库并上传文件
### 创建 Github 仓库

创建好就可以将地仓库推送到 Github 自己的仓库名下
### 配置 SSH 密钥
在安装并配置好本地 ssh ,并绑定邮箱的情况下,上传公钥到 github:进入 setting 界面,点击账户中的 profile,选择 ssh and GPG keys,点击 New SSH key,把 ~/.ssh/id_rsa.pub 中的全部内容复制到 Key 编辑框中,点击上传。
输入以下代码以测试链接畅通:
```shell
ssh -T [email protected]
```
如果出现如下报错,输入 yes 即可解决:

报错原因:新生成密钥的时候,git clone或者push的时候,经常会报这样的错误,少了一个known_hosts文件,本来密钥文件应该是三个,现在是两个,就会报这样的错误,此时选择yes回车之后,即可,同时生成缺少的known_hosts文件,解决后如图:

测试成功链接返回如图:

如果测试时出现报错,通过如下办法可以解决:

### 链接仓库
当我们的仓库创建完成,并配置好之后,就可以开始链接了

在这个界面我们选择 SSH 并复制仓库地址,通过以下命令进行链接:
```shell
git remote add origin [email protected]:ppqppl/FPGA--Develop-Diary.git
```
### 上传文件
下面,我们就可以将本地仓库中的文件上传到 github 上:
```shell
git push origin master
```
上传成功截图:

当提示键入用户名和密码时,直接输入即可
如果文件需要更新,则需要重新 commit ,然后重新上传
# GCC 编译全过程
本文仅总结了大部分常用的 GCC 命令,共大家学习参考
## GCC 简介
GCC 的意思也只是 GNU C Compiler 而已。经过了这么多年的发展,GCC 已经不仅仅能支持 C语言;它现在还支持 Ada 语言、C++ 语言、Java 语言、Objective C 语言、Pascal 语言、COBOL语言,以及支持函数式编程和逻辑编程的 Mercury 语言,等等。而 GCC 也不再单只是 GNU C 语言编译器的意思了,而是变成了 GNU Compiler Collection 也即是 GNU 编译器家族的意思了。另一方面,说到 GCC 对于操作系统平台及硬件平台支持,概括起来就是一句话:无所不在。
## 简单编译
实例程序 main.c 如下:
```c
#include
int main(void)
{
printf("Hello ppqppl! \n");
return 0;
}
```
这个程序,一步到位的编译命令如下:
```shell
gcc main.c -o main
```
实际上,上述的编译步骤分为四个阶段:
1.预处理(也叫预编译,Preprocessing)
2.编译(Compilation)
3.汇编(Assenbly)
4.连接(Linking)
### 预处理
主要包括一下过程:
1.将所有的 #define 删除,并展开所有宏定义,处理所有的条件预编译指令,例如 #if #ifdef 等
2.处理 #include 预编译命令,将被包含的文件插入到该预编译指令的位置
3.删除所有注释
4.添加行号和文件标识,以便编译时产生调试用的行号及编译错误警告的行号
5.保留所有的 #pragma 编译器指令,后迅速编译过程需要使用
预处理命令如下:
```shell
gcc -E main.c -o main.i
#或
gcc -E main.c
```
可以直接输出预处理后存放在 main.c 中的代码,gcc 的 -E 选项,可以让编译器在预处理后停止,-o 表示输出编译后的文件。并输出预处理结果。
### 编译为汇编代码
编译过程就是对预处理完的文件进行一系列的词法分析,语法分析,语义分析及优化后生成相应的汇编代码
GCC 命令如下:
```shell
gcc -S main.i -o main.s
```
gcc 的 -S 选项,表示在程序编译期间,在生成汇编代码后,停止
### 汇编
汇编过程调用对汇编代码进行处理,生成机器可以识别的指令,由于每一个汇编语句几乎都对应一条处理器指令,所以,汇编想对于编译过程简单一些,调用 Binutils 中的汇编器 as 根据汇编指令和处理器指令的对照表一一翻译即可
当程序由多个源代码文件构成时,每个文件都要先完成汇编,生成 .o 目标文件之后,才能进行链接
对于上一小节中生成的汇编代码文件 main.s,gas 汇编器负责将其编译为目标文件
```shell
gcc -c main.s -o main.o
# 或者直接使用 as 进行汇编
as -c main.s -o main.o
```
**注意:** hello.o目标文件为 ELF( ELF ) 格式的可重定向文件
关于 ELF 文件的介绍,请看:[GCC 一步到位]( https://www.cnblogs.com/ppqppl/articles/16717430.html "GCC 一步到位")的 ELF 部分
### 连接
链接分为:动态链接、静态链接
#### 静态链接
在编译阶段直接把静态库加入到可执行文件中,可执行文件较大。连接器将函数代码从所在地拷贝到最终执行程序中
#### 动态链接
链接阶段,仅仅只加入一些描述信息,而程序执行是从系统中制定位置把相应动态库加载到内存中
**注意:** 关于动态库与静态库的进一步内容,可以看我的另一篇文章:[GCC 入门之 静态库 与 动态库](https://www.cnblogs.com/ppqppl/articles/16711900.html "GCC 入门之 静态库 与 动态库")
#### 连接过程:
为创建可执行文件,连接器必须要完成的任务是:符号解析、重定位
符号解析:把目标文件中符号的定义和引用联系起来
重定位:把符号定义和内存地址对应起来然后修改所有对符号的作用
gcc 连接器是 gas 提供的,负责将程序的目标文件与所需的所有附加的目标文件连接起来,最终生成可执行文件。附加的目标文件包括静态连接库和动态连接库。
对于上一小节中生成的 main.o,将其与C标准输入输出库进行连接,最终生成程序 main
```shell
gcc main.o -o main
```
在命令行窗口,可以直接执行 ./test,运行结果如下:

## 多个程序文件的编译
通常整个程序是由多个源文件组成的,相应地也就形成了多个编译单元,使用 GCC 能够很好地管理这些编译单元。假设有一个由 main.c 和 sub1.c 两个源文件组成的程序,为了对它们进行编译,并最终生成可执行程序 main,可以使用下面这条命令:
```shell
gcc main.c sub1.c -o main
```
如果同时处理的文件不止一个,GCC 仍然会按照预处理、编译和链接的过程依次进行。如果深究起来,上面这条命令大致相当于依次执行如下三条命令:
```shell
gcc -c main.c -o main.o
gcc -c sub1.c -o sub1.o
gcc main.o sub1.o -o main
```
## 检错
```shell
gcc -pedantic main.c -o main
```
-pedantic 编译选项并不能保证被编译程序与 ANSI/ISO C 标准的完全兼容,它仅仅只能用来帮助 Linux 程序员离这个目标越来越近。
或者换句话说,-pedantic 选项能够帮助程序员发现一些不符合 ANSI/ISO C 标准的代码,但不是全部,事实上只有 ANSI/ISO C 语言标准中要求进行编译器诊断的那些情况,才有可能被 GCC 发现并提出警告。
除了-pedantic 之外,GCC 还有一些其它编译选项也能够产生有用的警告信息。这些选项大多以 -W 开头,其中最有价值的当数 -Wall 了,使用它能够使 GCC 产生尽可能多的警告信息。
```shell
gcc -Wall main.c -o main
```
GCC 给出的警告信息虽然从严格意义上说不能算作错误,但却很可能成为错误的栖身之所。一个优秀的 Linux 程序员应该尽量避免产生警告信息,使自己的代码始终保持标准、健壮的特性。所以将警告信息当成编码错误来对待,是一种值得赞扬的行为!所以,在编译程序时带上 -Werror 选项,那么 GCC 会在所有产生警告的地方停止编译,迫使程序员对自己的代码进行修改,如下:
```shell
gcc -Werror main.c -o main
```
# GCC 一步到位
## GCC 简介
GCC 的意思也只是 GNU C Compiler 而已。经过了这么多年的发展,GCC 已经不仅仅能支持 C语言;它现在还支持 Ada 语言、C++ 语言、Java 语言、Objective C 语言、Pascal 语言、COBOL语言,以及支持函数式编程和逻辑编程的 Mercury 语言,等等。而 GCC 也不再单只是 GNU C 语言编译器的意思了,而是变成了 GNU Compiler Collection 也即是 GNU 编译器家族的意思了。另一方面,说到 GCC 对于操作系统平台及硬件平台支持,概括起来就是一句话:无所不在。
## Binutils 工具包
一组二进制程序处理工具,包括:addr2line、ar、objcopy、objdump、as 、ld、ldd 、readelf 、size等。这一组工具是开发和调试不可缺少的工具
### addr2line
来将程序地址转换成其所对应的程序源文件及所对应的代码行,也可以得到所对应的函数。该工具将帮助调试器在调试的过程中定位对应的源代码位置
### as
主要用于汇编
### ld
主要用于连接
### ar
主要用于创建静态库
#### 静态库与动态库
将多个 .o 目标文件生成一个库文件,存在两种类型的库:静态库,动态库
Windows 中,静态库是后缀为 .lib 的文件,共享库是后缀为 .dll 的文件;在 Linux 系统中,静态库后缀为 .a ,动态库后缀为 .so
#### 静态库与动态库对比
代码载入时刻不同
静态库:编译过程中已经被载入可执行程序,体积较大
动态库:可执行程序运行时载入内存,编译过程仅简单引用,代码体积较小
Linux 系统中可以使用 ldd 命令查看可执行程序依赖的共享库
多个程序需要共享库,采用动态库更节省内存
### ldd
可以用于查看一个可执行程序的依赖的共享库
### objcopy
将一种对象文件翻译成另一种格式,比如将 .bin 转换为 .elf 等
### objdump
主要作用是反汇编
### readelf
显示有关 ELF 文件的信息
### size
列出可执行文件每个部分的尺寸和总尺寸,代码段、数据段、总大小等
## C 运行库
C 语言标准主要由两部分组成:一部分描述 C 的语法,另一部分描述 C 标准库。C 标准库定义了一组标准头文件,每个头文件中包含一些相关的函数、变量、型声明和宏定义,譬如常见的 printf 函数便是一个 C 标准库函数,其原型定义在 stdio 头文件中。
C 语言的标准仅仅定义了 C 标准库函数原型,并没有提供实现。因此,C 语言编译器通常需要一个 C 运行时库( C Run Time Libray , CRT )的支持。C 运行时库又常简称为 C 运行库。与 C 语言类似,c++ 也定义了自己的标准,同时提供相关支持库,成为 C++ 时运行库
## GCC 编译过程
实际上,上述的编译步骤分为四个阶段:
1.预处理(也叫预编译,Preprocessing)
2.编译(Compilation)
3.汇编(Assenbly)
4.连接(Linking)
具体编译过程,查看我的另一篇博客:[GCC 编译全过程](https://www.cnblogs.com/ppqppl/articles/16717275.html "GCC 编译全过程")
链接器链接后生成的最终文件为 ELF 格式可执行文件,一个 ELF 可执行文件通常被链接为不同的段,常见的段譬如:.text、.data、.rodata、.bss等
## ELF 文件解析
ELF文件格式如图:

位于 ELF Header 和 Section Header Table 之间的都是段(Section)。一个典型的 ELF 文件包含下面几个段:
.text:以编译程序的指令代码段
.rodata:ro 代表 read only ,即只读数据(譬如常数 const 等)
.data:已初始化的 C 程序全局变量和静态局部变量
.bss:未初始化的 C 程序全局变量和静态局部变量
.debug:调试符号表,调试器用此段的信息帮助调试
可以使用以下命令查看各个 Section 的信息:
```shell
readelf -S hello
```
显示内容如下:

### 反汇编 ELF
由于 ELF 文件无法像普通文本一样打开,如果要直接查看一个 ELF 文件包含的指令和数据,需要进行反汇编
使用 objdump -D 反汇编,代码如下:
```shell
objdump -D main
```
显示内容如下:

或者使用 objdump -S 将其反汇编并将其 C 语言代码混合显示,代码如下:
```shell
gcc -o main -g main.c
objdump -S main
```
显示内容如下:

# GCC 入门之 静态库 与 动态库
## 文件创建
在创建静态库和动态库之前,我们需要先创建我们需要的目标目录和文件
```shell
mkdir test1
touch hello.h
touch hello.c
touch main.c
```
hello.c 是函数库的源程序,其中包含公用函数 hello,该函数将在屏幕上输出 “Hello XXX!”。hello.h 为该函数库的头文件。main.c 为测试库文件的主程序,在主程序中调用了公用的函数 hello
程序如下:
hello.h:
```shell
#ifndef HELLO_H
#define HELLO_H
void hello(const char *name);
#endif //HELLO_H
```
hello.c:
```shell
#include
void hello(const char *name){
pritnf("Hello %s!\n",name);
}
```
main.c:
```shell
#include "hello.h"
int main(){
hello("ppqppl");
return 0;
}
```
## 编译
无论静态哭还是动态库都是由 .o 文件创建的,所以,要将 .c 文件编译成 .o 文件,直接使用 gcc 命令进行编译:
```shell
gcc -c hello.c hello.o
```
然后使用 ls 命令查看到编译出的文件,就说明编译成功:

## 由 .o 文件创建静态库
静态库文件夹的命名规范是以 lib 为前缀,紧接着跟静态库名,扩展名为 .a 。例如:我们将创建的静态库命名为 myhello ,则静态库文件名就是 libmyhello.a 。在创建和使用静态库时,需要注意这点。创建静态库使用 ar 命令,在系统提示符下键入一下命令,将创建静态库文件 libmyhello.a :
```shell
ar -crv libmyhello.a hello.o
```
我们同样使用 ls 命令查看是否创建成功,如果成功会显示如下图:

### 使用静态库
可以直接通过以下代码使用静态库:
```shell
# 方法一
sudo gcc -o hello main.c -L. -lmyhello
# 方法二
sudo gcc main.c libmyhello.a -o hello
# 方法三
# 先生成 main.o
sudo gcc -c main.c -o main.o
# 再生成可执行文件
sudo gcc -o hello main.o libmyhello.a
```
### 测试是否成功

删除静态库,测试共用函数是否完全链接到目标文件 hello 中
```shell
sudo rm libmyhello.a
./hello
```
如果正常执行输出,就说明已经成功链接,成功链接测试结果如下图:

## 由 .o 文件创建动态库
动态库文件与静态库文件命名规范类似,只是后缀不同,动态库的后缀为 .so
用 gcc 命令创建动态库:
```shell
gcc -shared -fPIC -o libmyhello.so hello.o
```
我们同样使用 ls 命令查看是否创建成功,如果成功会显示如下图:

### 使用动态库
动态库与静态库使用方法完全一样,gcc 命令如下:
```shell
# 方法一
gcc -c hello main.c -L .lmyhello
# 方法二
gcc main.c libmyhello.so -o hello
# 方法三
# 先生成 main.o
sudo gcc -c main.c -o main.o
# 再生成可执行文件
sudo gcc -o hello main.o libmyhello.a
```
动态库与静态库有一些不同,使用静态库会从生成库的位置寻找静态库,而动态库,是从 `/usr/lib` 寻找,直接运行 hello 可执行文件会报错如下图:

所以,需要将动态库移动到上述目录下,才能正确运行,运行结果如下图:


### 测试是否成功

删除静态库,测试共用函数是否完全链接到目标文件 hello 中
```shell
sudo rm libmyhello.so
./hello
```
运行输出结果如下图:

说明在链接动态库之后,动态库源文件不能删除,否则程序将不能运行
## 静态库与动态库对比
这里主要介绍搜索路径的对比
在 Linux 系统中,GCC 编译链接时的动态库搜索路径的顺序通常为:首先从 GCC 命令参数 -L 制定的路径寻找; 再从环境变量 LIBRARY_PATH 指定的路径寻址; 再从默认路径 /lib、/usr/lib、/usr/local/lib 寻找
在 Linux 系统中,执行二进制文件时的动态哭搜索路径的顺序通常为:首先搜索编译目标代码时制定的搜索路径; 再从环境变量 LD_LIBRARY_PATH 指定的路径寻址; 再从配置文件 /etc/ld.so.conf 中指定的动态库搜索路径; 再从默认路径 /lib、/usr/lib 寻找
## 静态库与动态库共存?
从上述步骤我们可以成功链接静态库和动态库,我们也可以知道链接静态库之后,库文件可以删除,链接动态库之后,库文件不能删除,并且还要放到指定的路径之下`/usr/lib`
那么,如果我们同时链接静态库和动态库,会调用静态库还是动态库还是直接报错?
首先,先编译生成静态库和动态库,按照前面介绍的方法即可。

然后编译生成可执行文件,并运行:

如图可知,由于链接动态库和静态库的路径可能有重合,所以如果在路径中,我们在编译的时候同时链接静态库和动态库(同名时),会优先使用动态库,如果要链接静态库,则可以制定 gcc 选项 - static ,该选项会强制使用静态库进行链接
在 Linux 系统中,可以用 ldd 命令查看一个可执行程序依赖的共享库
链接器链接后生成的最终文件为 ELF 格式可执行文件,一个 ELF 可执行文件通常被链接为不同的段,常见的段譬如:.text、.data、.rodata、.bss等
**注意:** 关于 ELF 文件的介绍,请看:[GCC 一步到位]( https://www.cnblogs.com/ppqppl/articles/16717430.html "GCC 一步到位")的 ELF 部分
## 实例分析
通过 sub1.o add1.o 生成静态库:

链接静态库,并查看文件大小如下:

链接动态库,并查看文件大小如下:


由此可见,两次生成的可执行文件大小相同,但是动态库的大小明显要大于静态库大小
## 参数补充
### 补充 1:编译参数解析
最主要的是 GCC 命令行的一个选项:
-shared 该选项指定生成动态连接库(让连接器生成 T 类型的导出符号表,有时候也生成弱连接 W 类型的导出符号),不用该标志外部程序无法连接。相当于一个可执行文件。
-fPIC 表示编译为位置独立的代码,不用此选项的话编译后的代码是位置相关的所以动态载入时是通过代码拷贝的方式来满足不同进程的需要,而不能达到真正代码段共享的目的。
-L. 表示要连接的库在当前目录中;(多个库:在编译命令行中,将使用的静态库文件放在源文件后面就可以了。比如:
gcc -L/usr/lib myprop.c libtest.a libX11.a libpthread.a -o myprop
其中-L/usr/lib 指定库文件的查找路径。编译器默认在当前目录下先查找指定的库文件,如前面的“法二 #gcc main.c libmyhello.a -o hello”)
-lmyhello 编译器查找动态连接库时有隐含的命名规则,即在给出的名字前面加上 lib,后面加上.so 或.a 来确定库的名称 libmyhello.so 或 libmyhello.a。
LD_LIBRARY_PATH 这个环境变量指示动态连接器可以装载动态库的路径。
当然如果有 root 权限的话,可以修改/etc/ld.so.conf 文件,然后调用 /sbin/ldconfig 来达到同样的目的,不过如果没有 root 权限,那么只能采用输出 LD_LIBRARY_PATH 的方法了。
调用动态库的时候有几个问题会经常碰到,有时,明明已经将库的头文件所在目录 通过 “-I”include 进来了,库所在文件通过 “-L”参数引导,并指定了“-l”的库名,但通过 ldd 命令察看时,就是死活找不到你指定链接的 so 文件,这时你要作的就是通过修改LD_LIBRARY_PATH 或者/etc/ld.so.conf 文件来指定动态库的目录。通常这样做就可以解决库无法链接的问题了。
### 补充 2:
从上述可知,如何找到生成的动态库有 3 种方式:
(1)把库拷贝到/usr/lib 和/lib 目录下。
(2)在 LD_LIBRARY_PATH 环境变量中加上库所在路径。例如动态库 libhello.so 在/home/example/lib 目录下:
```shell
$export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/example/lib
```
(3) 修改/etc/ld.so.conf 文件,把库所在的路径加到文件末尾,并执行 ldconfig 刷新。这样,加入的目录下的所有库文件都可见。
附:像下面这样指定路径去连接系统的静态库,会报错说要连接的库找不到:
g++ -o main main.cpp -L/usr/lib libpthread.a
必须这样 g++ -o main main.cpp -L/usr/lib -lpthread 才正确 。
自定义的库考到/usr/lib 下时,g++ -o main main.cpp -L/usr/lib libpthread.a libthread.a libclass.a 会出错,但是这样 g++ -o main main.cpp -L/usr/lib -lpthread -lthread -lclass 就正确了
# Ubuntu 安装编译 opencv
## 安装环境准备
### 配置 Ubuntu 镜像源
由于部分镜像源并没有将软件包的版本更新到最新,国外官方镜像源访问与下载速度较慢,所以需要更新镜像源为以下几种:
阿里镜像源
```shell
sudo bash -c "cat << EOF > /etc/apt/sources.list && apt update
deb http://mirrors.aliyun.com/ubuntu/ jammy main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ jammy main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ jammy-security main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ jammy-security main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ jammy-updates main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ jammy-updates main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ jammy-proposed main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ jammy-proposed main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ jammy-backports main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ jammy-backports main restricted universe multiverse
EOF"
```
清华大学镜像源
```shell
sudo bash -c "cat << EOF > /etc/apt/sources.list && apt update
deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy main restricted universe multiverse
# deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy main restricted universe multiverse
deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-updates main restricted universe multiverse
# deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-updates main restricted universe multiverse
deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-backports main restricted universe multiverse
# deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-backports main restricted universe multiverse
deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-security main restricted universe multiverse
# deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-security main restricted universe multiverse
EOF"
```
163镜像源
```shell
sudo bash -c "cat << EOF > /etc/apt/sources.list && apt update
deb http://mirrors.163.com/ubuntu/ jammy main restricted universe multiverse
deb http://mirrors.163.com/ubuntu/ jammy-security main restricted universe multiverse
deb http://mirrors.163.com/ubuntu/ jammy-updates main restricted universe multiverse
deb http://mirrors.163.com/ubuntu/ jammy-proposed main restricted universe multiverse
deb http://mirrors.163.com/ubuntu/ jammy-backports main restricted universe multiverse
deb-src http://mirrors.163.com/ubuntu/ jammy main restricted universe multiverse
deb-src http://mirrors.163.com/ubuntu/ jammy-security main restricted universe multiverse
deb-src http://mirrors.163.com/ubuntu/ jammy-updates main restricted universe multiverse
deb-src http://mirrors.163.com/ubuntu/ jammy-proposed main restricted universe multiverse
deb-src http://mirrors.163.com/ubuntu/ jammy-backports main restricted universe multiverse
EOF"
```
中科大镜像源
```shell
sudo bash -c "cat << EOF > /etc/apt/sources.list && apt update
deb https://mirrors.ustc.edu.cn/ubuntu/ jammy main restricted universe multiverse
deb-src https://mirrors.ustc.edu.cn/ubuntu/ jammy main restricted universe multiverse
deb https://mirrors.ustc.edu.cn/ubuntu/ jammy-updates main restricted universe multiverse
deb-src https://mirrors.ustc.edu.cn/ubuntu/ jammy-updates main restricted universe multiverse
deb https://mirrors.ustc.edu.cn/ubuntu/ jammy-backports main restricted universe multiverse
deb-src https://mirrors.ustc.edu.cn/ubuntu/ jammy-backports main restricted universe multiverse
deb https://mirrors.ustc.edu.cn/ubuntu/ jammy-security main restricted universe multiverse
deb-src https://mirrors.ustc.edu.cn/ubuntu/ jammy-security main restricted universe multiverse
deb https://mirrors.ustc.edu.cn/ubuntu/ jammy-proposed main restricted universe multiverse
deb-src https://mirrors.ustc.edu.cn/ubuntu/ jammy-proposed main restricted universe multiverse
EOF"
```
系统可以同时包括四种镜像源,确保能够及时搜索到所有的软件的最新版本
### 环境准备
安装 opencv Ubuntu 版本最低要求:18.04
```shell
# c、c++ 编译环境准备
sudo apt-get install gcc g++ cmake wget unzip
# 安装依赖
sudo apt-get install build-essential libgtk2.0-dev libavcodec-dev libavformat-dev libjpeg-dev libswscale-dev libtiff5-dev
sudo apt-get install libgtk2.0-dev
sudo apt-get install pkg-config
```
如果在安装过程中出现如下报错:
```shell
下列软件包有未满足的依赖关系:
libgtk2.0-dev : 依赖: libgdk-pixbuf2.0-dev (>= 2.21.0) 但是它将不会被安装
E: 无法修正错误,因为您要求某些软件包保持现状,就是它们破坏了软件包间的依赖关系。
```
就是说当前镜像源网站的软件包版本不是最新版本,需要进行换源,保证新的镜像源有符合安装版本的安装包
## 下载与安装
### 下载opencv
opencv 可以从靠谱的镜像源进行下载,或者直接通过命令进行下载
opencv 下载镜像源:https://gitcode.net/opencv 这是 opencv 官方为国人提供的镜像下载网址(有条件的使用官网下载)
命令下载:
```shell
git clone https://gitcode.net/opencv/opencv # 此网站为国内网站,可以放心下载
```
使用 unzip 命令解压 opencv,命令如下:
```shell
upzip xxx.zip # xxx是你的压缩包名称
```
解压完成如下图

然后将文件移动到 opencv 文件夹中
```shell
mv xxx opencv # xxx 是你的文件加名称
```
### 安装 opencv
创建 build 文件夹并进入
命令如下:
```shell
cd opencv
mkdir build
cd build
```
创建成功如下图:

使用 cmake 调配参数
```shell
sudo cmake -D CMAKE_BUILD_TYPE=Release -D CMAKE_INSTALL_PREFIX=/usr/local ..
```
或者可以通过 cmake-gui 进行可视化安装,代码如下:(本篇不进行可视化安装演示,仅给出代码)
```shell
sudo apt-get install cmake-qt-gui
sudo cmake-gui
```
cmake 配置成功如图:

进行编译
```shell
sudo make -j线程数
```
编译完成标志,如下图:

编译完成就可以直接开始安装
```shell
sudo make install
```
安装完成如下图:

### 添加路径
用 vim 或 gedit 打开配置文件进行配置,配置文件路径:/etc/ld.so.conf
```shell
sudo vim /etc/ld.so.conf
# 打开后添加如下代码:
include /usr/local/lib
```
保存并关闭后,运行如下代码,确定并执行配置:
```shell
运行sudo ldconfig
```
### 环境配置
修改bash.bashrc文件:
```shell
sudo vim /etc/bash.bashrc
```
在文件末尾添加如下内容:
```shell
PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig
export PKG_CONFIG_PATH
```
如下图所示:

然后执行如下命令,对该文件进行类似重载的操作:
```shell
source /etc/bash.bashrc
# 注意:次命令不能用 sudo 执行
```
## 安装完成
最后输入以下命令,如果成功显示 opencv 版本号,即编译配置成功:
```shell
pkg-config opencv --modversion
```
成功显示如下图

# 初遇 opencv
## 调用电脑摄像
test1.cpp 文件内容如下:
```c++
#include
using namespace cv;
int main()
{
// 从摄像头读取视频
VideoCapture capture(0);
// 循环播放每一帧
while(1)
{
Mat frame; // 定义一个 Mat 变量,用于存储每一帧的图像
capture >> frame; // 读取当前帧
imshow("读取视频帧",frame); // 显示当前帧
waitKey(30); // 延时30ms
}
system("pause");
return 0;
}
```
本代码循环条件是 1 ,意味着会一直循环显示摄像,知道使用 ctrl+c 强制退出,要想要控制程序关闭退出,只需要修改循环条件,将 waitKey() 从循环中刷新,改为判断条件,在刷新的同时接收按键信息:
```c++
// 无限循环,只能强制退出
while(1)
// 可以按键退出
while(waitKey(30) != 按键对应的键值)
```
## 打开本地视频播放
如果不是直接调用电脑的摄像设备,而是调用电脑的中的视频播放,只需要修改一下部分即可:
```c++
// 需要修改的部分
VideoCapture capture(0);
// 修改后的部分
VideoCapture("视频路径");
```
## 编译与运行
### gcc 编译
gcc 编译命令如下:
```shell
sudo g++ test1.cpp -o test1 `pkg-config --cflags --libs opencv`
```
#### 我们是如何获取 opencv 头文件的,又是如何 lib 库文件路径的?
在上面的编译命令中,我们其实使用了一个工具 " pkg-config " ,主要有以下几个功能:
1.检查库的版本号,如果需要的库不满足要求,会打印出错误消息,避免链接错误版本的库文件,这也就是我们大多时候报错的原因
2.获得编译预处理参数,如宏定义,头文件的位置
3.获得链接参数,如库及依赖的其他库的位置,文件名及其他一些链接参数
4.自动加入所依赖的其他库
所有有了这个工具之后我们的编译就很方便了(不过在此之前你要确保你安装的OpenCV的安装链接库文件的目录下有一个 pkgconfig 文件夹,在该文件夹里面有个 opencv.pc 的文件,其实这就是 pkg-config 下 OpenCV 的配置文件)
使用 pkg-config 时,选项 --cflags 它是用来指定程序在编译时所需要头文件所在的目录,选项 --libs 则是指定程序在链接时所需要的动态链接库的目录
在上述编译过程中,实际上应该如下:
```shell
pkg-config --cflags --libs opencv-I/usr/local/include/opencv -I/usr/local/include -L/usr/local/lib -lopencv_calib3d -lopencv_contrib -lopencv_core -lopencv_features2d -lopencv_flann -lopencv_gpu -lopencv_highgui -lopencv_imgproc -lopencv_legacy -lopencv_ml -lopencv_nonfree -lopencv_objdetect -lopencv_ocl -lopencv_photo -lopencv_stitching -lopencv_superres -lopencv_ts -lopencv_video -lopencv_videostab -lrt -lpthread -lm -ldl
```
所以,如下编译可以有相同作用:
```shell
gcc main.c -I/usr/local/include/opencv -I/usr/local/include -L/usr/local/lib -lopencv_calib3d -lopencv_contrib -lopencv_core -lopencv_features2d -lopencv_flann -lopencv_gpu -lopencv_highgui -lopencv_imgproc -lopencv_legacy -lopencv_ml -lopencv_nonfree -lopencv_objdetect -lopencv_ocl -lopencv_photo -lopencv_stitching -lopencv_superres -lopencv_ts -lopencv_video -lopencv_videostab -lrt -lpthread -lm -ldl
```
### Makefile 编译
建立 makefile 文件内容如下:
```shell
.PHONY:clean
CXX = g++
LIBS = `pkg-config --libs opencv`
CFLAGS = `pkg-config --cflags opencv`
CPPCFLAGS = -g -std = c++11 -Werror
SRCS = test1.cpp
TARGET = test1.out
OBJS = test1.o
# 编译生成可执行文件
$(TARGET): $(OBJS)
@echo "Start Compiling……"
@echo $(CXX)
$(CXX) $(INC) $(CPPFLAGS) $(OBJS) -O $(TARGET) $(LIBS)
@echo "Compile Done!"
# 下面是顺序编译生成 .o 文件
# 每个 .c 文件都会编译生成对应的 .o 文件
# < 表示目标依赖表中的第一个依赖文件; @ 表示生成的对应的目标文件
$(OBJS):%.o:%.c
$(CXX) $(INC) $(CPPFLAGS) -c $< -o $@
clean:
-rm -r *.o $(TARGET)
```
编译指令如下:
```shell
make
# 编译运行后,应删除编译产生文件,以便后续再次编译,删除命令如下:
make clean
```
## 运行
直接运行可执行程序,成功调用电脑摄像头,如下图:

## 变量介绍
### Mat
Mat 是一个类,使用的非常广泛,下面只介绍他的默认构造函数
#### 默认构造函数
```c++
cv::Mat::Mat()
```
默认构造函数:生成一个矩阵并由 OpenCV 提供的函数(一般是 Mat::create() 和 cv::imread() )来分配储存空间。
Mat 类可以分为两个部分:矩阵头和指向像素数据的矩阵指针
矩阵头:包括数字图像的矩阵尺寸、存储方法、存储地址和引用次数等,矩阵头的大小是一个常数,不会随着图像的大小而改变,但是保存图像像素数据的矩阵则会随着图像的大小而改变,通常数据量会很大,比矩阵头大几个数量级。这样,在图像复制和传递过程中,主要的开销是由存放图像像素的矩阵而引起的。因此,OpenCV 使用了引用次数,当进行图像复制和传递时,不再复制整个 Mat 数据,而只是复制矩阵头和指向像素矩阵的指针,例如:
```c++
CV::Mat a
a = CV::imread("图片路径")
CV::Mat b = a
```
上面的a,b有各自的矩阵头,但是其矩阵指针指向同一个矩阵,也就是其中任何一个改变了矩阵数据都会影响另外一个。那么,多个 Mat 共用一个矩阵数据,最后谁来释放矩阵数据呢?
这就是引用计数的作用,当 Mat 对象每被复制一次时,就会将引用计数加1,而每销毁一个 Mat 对象(共用同一个矩阵数据)时引用计数会被减 1,当引用计数为 0 时,矩阵数据会被清理。
通俗一点来说,就是通过矩阵来读入一个图片的所有像素,然后在显示的时候,在通过矩阵还原图片的像素点,达到显示图片的效果
### waitkey
waitKey函数用于显示的延迟。例如,waitKey(0) 将无限显示窗口,直到按下任意按键退出延迟事件(适用于显示图像)。如果 delay 大于0,例如,waitKey(30) 将每隔至少 30ms 显示视频的一帧图像(适用于显示视频帧),如果要按键退出,则需要将 waitKey(30) 与一个按键值(ASCII码)比较。
waitKey() 函数的功能就是在不断刷新图像,频率为 delay ,单位是 ms ,返回值为当前键盘按下的值,没有按键时返回 -1
显示图片和视频时,会在 imshow()时,通常会在后面加上 while(cvWaitKey(n)==key) 为大于等于 0 的数即可,那么程序将在此处循环运行直到按键响应为 key 时之后继续,如果没有设置 key 值,只是设置了 waitKey ,则就会一直显示视频界面,不一会退出
delay : 为 0 时,则会一直显示这一帧,“delay”,在显示视频和摄像头时有用,用于设置在显示完一帧图像后程序等待“delay" ms 再显示下一帧视频
如果程序想响应某个按键,可利用 if(cvWaitKey(1)==Keyvalue);经常程序里面出现 if( cvWaitKey(10) >= 0 ) 是说 10ms 中按任意键进入此 if 块
**注意:**在 imshow 之后如果没有 waitKey 语句则不会正常显示图像。
## 报错处理
### 编译报错
一般编译 cpp 文件的过程中会出现如下报错:
```shell
/usr/bin/ld: /tmp/ccNS2ClW.o: undefined reference to symbol '_ZNSt8ios_base4InitD1Ev@@GLIBCXX_3.4'
/usr/bin/ld: /lib/x86_64-linux-gnu/libstdc++.so.6: error adding symbols: DSO missing from command line
collect2: error: ld returned 1 exit status
```
说明缺少需要的库,只要在编译时链接所需的库文件即可,上述报错缺少的是 libstdc++ 库,直接链接库文件即可
更直接的原因是:我们所编译的文件是 cpp 文件,应该使用 g++ 进行编译,而不是使用 gcc ,如果我们要强行使用 gcc 进行编译,就需要链接 libstdc++ 的库,如果直接使用 g++ 进行编译,就没有该报错问题
### 运行报错
如果在运行时报错如下:
```shell
[ WARN:0] VIDEOIO(V4L2:/dev/video0): can't open camera by index
terminate called after throwing an instance of 'cv::Exception'
what(): OpenCV(3.4.16) /home/ppqppl/文档/opencv/modules/highgui/src/window.cpp:382: error: (-215:Assertion failed) size.width>0 && size.height>0 in function 'imshow'
```
如果是打开图片:
尺寸都是0(前提是图片正确),说明没有读取到有效图片
根本原因是路径不正确,还有一个隐藏问题是 有中文路径
如果是打开并链接摄像:
可能是链接摄像失败,或者是电脑根本没有摄像设备
如果运行基于 Opencv 有显示图片的窗口时,在按键跳出循环时,出现如下报错:
```shell
Failed to load module "canberra-gtk-module"
```
不必担心,只是缺少 module ,安装对应的 module 即可:
```shell
sudo apt-get install libcanberra-gtk-module
```
## 用手机充当电脑摄像
如果电脑在 Ubuntu 环境下而且没有摄像头,又需要使用摄像,那该怎么办?
最简单的办法就是在手机和电脑上同时安装 DroidCam ,可以通过 WiFi/LAN 或 USB 连接手机作为摄像驱动而使用
DroidCam 驱动官网:[dev-47](https://www.dev47apps.com/ "dev-47")
安装可以直接参考官网教程