Qeuroal's Blog

静幽正治

基础

认识 Latex

命令与环境

构成

反斜杠加上命令名称,再加上花括号内的参数

1
\documentclass{ctexart}

保留字符

符号 说明
# 自定义命令时,用于标明参数序号。
$ 数学环境命令符。
% 注释符,其后的该行命令都会视为注释。如果在行末添加这个命令,可以防止 LaTeX 在行末插入一些奇怪的空白符。
^ 数学环境中的上标命令符。
& 表格环境中的跳列符。
_ 数学环境中的下标命令符。
{ 与 } 用于标记命令的必选参数,或者标记某一部分命令使其成为一个整体。
\ 用于开始 LaTeX 命令。

导言区

1
2
3
4
\documentclass[option]{doc-class}
\begin{document}
...
\end{document}

在语句 \begin{document} 之前的内容称为==导言区==,导言区即模板定义

doc-class

doc-class(文档类) 类别
article 科学期刊、演示文稿、短报告、邀请函
proc 基于 article 的会议论文集
report 多章节的长报告、博士论文、短篇书
book 书籍
slides 幻灯片,使用了大号 Scans Serif 字体

此外还有 beamer 宏包定义的 beamer 文档类,其常用于创建幻灯片。

options (可选项)

options 取值
字体 默认 10 pt,可选 11 pt 和 12 pt
页面方向 默认 portrait (竖向),可选 landscape (横向)
纸张尺寸 默认 letterpaper ,可选 a4paper 、b5paper 等
分栏 默认 onecolumn ,可选 twocolumn
双面打印 有 oneside/twoside 两个选项,用于排版奇偶页, article/report 默认单面
章节分页 有 openright/openany 两个选项,决定在奇数页或任意页开启新页。注意,article 是没有 chapter (章)命令的,默认任意页
公式对齐 默认居中;可改为左对齐 fleqn ;默认编号居右,可改为左对齐leqno
草稿选项 默认 final ,可改为 draft ,使行溢出的部分显示为黑块

宏包

导言区最常见的是宏包的加载工作,命令形如:

1
\usepackage{package}

宏包是指一系列已经制作好的功能“模块”,在你需要使用一些非原生 LaTeX 功能时,只需调用这些宏包就可以了

学习宏包

按 Win+R 组合键调出运行对话框,输入 texdoc 加上宏包名称即可打开宏包 pdf 帮助文档,例如:texdoc xeCJK

文件输出

pdf

后缀 说明
.sty 宏包文件。
.cls 文档类文件。
.aux 用于存储交叉引用信息的文件,因此,在更新交叉引用(公式编号、大纲级别)后,需要编译两次才能正常显示。
.log 日志,记录上次编译的信息。
.toc 目录文件。
.lof 图形目录。
.lot 表格目录。
.idx 如果文档中包含索引,该文件用于存储索引信息。
.ind 索引记录文件。
.ilg 索引日志文件。
.bib 参考文献数据文件。
.bbl 生成的参考文献记录。
.bst 模板。
.blg 日志。
.out hyperref 宏包生成的 pdf 书签记录。

异常处理

删除文件夹下除了 tex 以外的文件再编译

注意

在某些独占程序打开了以上的文件时(比如用Acrobat 打开了 pdf 文件),编译也可能出现错误。请在编译时确保关闭这些独占程序。

标点与强调

英文符号 |、<、>、+、= 一般用于数学环境

  • 在文本中使用,请在它们两侧加上 $
  • 大于小于号:你应该使用 \textgreater\textless 命令

引号

  • 左单引号是重音符 `
  • 右单引号是常用的引号符
  • 左双引号就是连续两个重音符
  • 英文下的引号嵌套需要借助 \thinspace 命令分隔

省略号

英文的省略号使用 \ldots 这个命令

强调:粗与斜

\emph{text}

  • 中文:强调文本
  • 细纹:斜体

下划线与删除线

  • LaTeX 原生提供的 \underline 命令简直烂得可以
  • ==建议==你使用 ulem 宏包下的 uline 命令,它还支持换行文本
命令 说明
\uline{} 下划线
\uuline{} 双下划线
\dashuline{} 虚下划线
\dotuline{} 点下划线
\uwave{} 波浪线
\sout{} 删除线
\xout{} 斜删除线

注意

ulem 宏包重定义了 \emph 命令:

  • 强调命令会对内容加下划线

  • 嵌套使用两次强调命令则会对内容加双下划线

通过宏包的 normalem 选项可以取消这个更改:\usepackage[normalem]{ulem}

其他

  • 角度符号或者温度符号
  • 欧元符号可能需要用到 textcomp 宏包支持的 \texteuro 命令
  • 千位分隔位
    • 不想它在中间断行,就在外侧再加上一个 \mbox 命令
  • 注音符号
  • 音标:
    • tipa 宏包
    • numprint 宏包
    • siunitx 宏包
  • hologo 宏包可以输出许多TEX 家族标志

格式控制

符号说明

符号 说明
pt point,磅。
pc pica,1 pc = 12 pt,四号字。
in inch,英寸,1 in = 72.27 pt。
bp bigpoint,大点,1 bp = in。
cm centimeter,厘米,1 cm = in。
mm millimeter,毫米,1 mm = cm。
sp scaled point,TEX 的基本长度单位,1 sp = pt。
em 当前字号下,大写字母M的宽度。
ex 当前字号下,小写字母x的高度。

长度宏

说明
\textwidth 页面上文字的总宽度,即叶宽减去两侧边距
\linewidth 当前行允许的行宽

可变长度

  • 5 pt plus 3 pt minus 2 pt:表示一个能收缩到 3 pt 也能伸长到 8 pt 的长度
  • 直接使用倍数:1.5\parindent

特殊空格

\hspace\vspace

空格与换行

  • 多个空格、换行会被视为一个
  • 禁止 LaTeX 在某个空格处的换行,将空格用 ˜ 命令替代即可,比如: Fig.˜ 8

换行

  • 会自动换行
  • 分段:两个回车 (用于正文换行)
  • 空白段落(空白行)
    • 两个回车 + \mbox{} + 两个回车
    • \par: 生成一个带缩进的新段
  • 强制换行: \\
    • 缺点:下一行段首缩进会消失

段落间距离

  • 默认: 0pt plus 1pt
  • 控制命令 \parskip\setlength{\parskip}{0pt plus 1pt}
  • 首字下沉:lettrine 宏包

分页

  • \newpage 命令开始新的一页
  • \clearpage 命令清空浮动体队列,并开始新的一页
  • \cleardoublepage 命令清空浮动体队列,并在偶数页开始新的一页
  • 连续新开两页:在 \newpage 中间加上一个空的箱子(\mbox{}),\newpage\mbox{}\newpage

缩进、对齐与行距

缩进

  • 宏包:借助 indentfirst 宏包
  • 设置距离:使用 \setlength\parindent{2em} 这样的命令来设置缩进距离
  • 段首强制取消缩进:在段首使用 \noindent 命令

英文的每节第一段的段首==允许没有缩进==

对齐

默认使用两端对齐的排版方式

设置

可以使用 flushleft 、flushright 、center 这三种环境来构造居左、居中、居右三种效果。

特殊的 \centering 命令常常用在环境内部(或者一对花括号内部),以实现居中的效果。

尽量用 center 环境代替这个老旧的命令。类似地还有 \raggedleft 实现居右, \raggedright 实现居左

字体与颜色

中西文“斜体”

中文

汉字没有加斜体

汉字字体里面也不一定有加粗体的设计

英文

  • 加斜是指某种字族的 Italy 字系
  • 斜体,是指 Slant 字族

使用

  • 行文中表强调时使用的是前者;
  • 在 MS Word 等软件中看到的倾斜字母 I ,也代表前者。

原生字体命令

字族

命令 描述
\rmfamily 把字体设置为 Roman 罗马字族
\sffamily 把字体设置为 Sans Serif 无衬线字族
\ttfamily 把字体设置为 Typewritter 等宽字族

字系

命令 描述
\bfseries 粗体 BoldSeries 字系属性
\mdseries 中粗体 MiddleSerie 字系属性

字形

命令 描述
\upshape 竖直字形
\slshape 斜体字形
\itshape 强调体字形
\scshape 小号大写字形

临时改变字体

临时改变字体,使用 \textrm\textbf 这类命令

注意

  • 字族、字系、字形三种命令是互相独立的,可以任意组合使用

  • 这种复合字体的效果有时候无法达到(因为没有对应的设计)

    LaTeX 会针对这种情况给出警告,但仍可以编译,只是效果达不到预期。

自定义命令

在文中多次使用某种字体变换,可以将其自定义成一个命令

字号

默认的“标准”字号

  • 在documentclass 的选项中设置的 12 pt(如果你设置了的话)

相对字号命令

ctex 宏包

ctex 宏包的 \zihao 命令的参数 08 以及 -0-8 表示初号到八号、小初号到小八号

日常使用的小四号为 12 pt,五号为 10.5 pt

设置特殊的字号

1
\fontsize{font-size}{line-height}{\selectfont <text>}
  • font-size: 填数字,单位pt
  • line-height: 填 \baselineskip,表示行与行之间的基线间距(即行距),默认是 1.2 倍文字高。

默认字体

全文的默认字体使用 \rmfamily 族的字体

通过重定义的方式更改它,使 \rmfamily\textrm 命令都指向新的字体,甚至把默认字体改为 sf/tt 字族

1
2
3
4
5
6
\renewcommand{\rmdefault}{font-name}
% 默认字体改为sf字族,也可以用 \ttdefault
\renewcommand{\familydefault}{\sfdefault}
\renewcommand{\sfdefault}{font-name}
% 如果你排版 CJK文档,还需要更改CJK的默认字体
\renewcommand{\CJKfamilydefault}{\CJKsfdefault}

西文字体

预包含西文字体

使用

引言区定义

1
2
3
\newcommand{\myfont}[2]{{\fontfamily{#1}\selectfont #2}}
% 可更改默认字体,同理可改 sfdefault
\renewcommand{\rmdefault}{ptm}

正文中

1
Let's change font to \myfont{ppl}{Palatino}!

本地安装字体

使用 fontspec 宏包

一般使用 fontspec 宏包来选择本地安装 的字体。注意,该宏包可能会明显增加编译时间

1
2
3
\usepackage{fontspec}
\newfontfamily{\lucida}{Lucida Calligraphy}
\lucida{This is Lucida Calligraphy}

该宏包的 \setmathrm/sf/tt\setboldmathrm 命令可以更改数学环境中调用的字体。

简单加载 txtfont 宏包

将西文字体设置为Roman 体,同时设置好数学字体。

简单字体宏包还有cmbright ,以及提供 Palatino 字体的 pxfonts ,前者提供的 CM Bright 与 TeX 默认字体 Computer Modern 协调得不错

中文支持与CJK字体

ctex 宏包

直接定义了新的中文文档类 ctexart 、ctexrep 与 ctexbook ,以及 ctexbeamer 幻灯文档类。

xeCJK 宏包

在使用 时,如果你使用 ctex 文档类,它会在底层调用 xeCJK 宏包,所以你无须再显式地加载它。当然你也可以使用原生文档类,然后逐一汉化参数内容。

示例

在引言区

1
2
3
\usepackege[slantfont,boldfont]{xeCJK}
\xeCJKsetup{CJKMath=true}
\setCJKmainfont[BoldFont=SimHei]{Simsun}

颜色

使用 xcolor 宏包

1
2
\usepackage{xcolor}
\definecolor{keywordcolor}{RGB}{34,34,250}

指定颜色的text

1
{\color{color-name}{text}}

调色

1
2
3
4
5
6
% 70%红色
{\textcolor{red!70}}
% 50蓝,20黑,30白
{\textcolor{blue!50!black!20!white!30!}{50蓝,20黑,30白}}
% 黄色互补色
{\textcolor{-yellow}{黄色互补色}}

引用与注释

标签与引用

没看懂

脚注、边注和尾注

没看懂

摘要

没看懂

参考文献

natbib 宏包

正式排版:封面、大纲与目录

封面

封面的内容在导言区进行定义,==一般写在所有宏包、自定义命令之后==

命令如下:

1
2
3
\title{Learning Latex}
\author{Qeuroal
\date{text}

使用封面

document 环境内的第一行写上 \maketitle ,这样就能产生一个简易的封面。

1
2
3
4
5
\begin{document}
\maketitle


\end{document}

标题页的脚注用 \thanks 命令完成。

注意

  • \title 和 \author ==是必须定义的==
  • date
    • \date 如果省略:会自动以编译当天的日期为准,格式形如: January 1, 1970。
    • 不想显示日期,可以写 \date{}

大纲与章节

命令 大纲级别
\part 部分,这个大纲不会打断 chapter 的编号。
\chapter 章,article 的文档类不包含本大纲级别。
\section 节。
\subsection 次节,默认 report/book 文档类中本级别及以下的大纲不进行编号,也不纳入目录。
\subsubsection 小节,默认 article 文档类中本级别及以下的大纲不进行编号,也不纳入目录。
\paragraph 段,极少使用。
\subparagraph 次段,极少使用。

计数器与列表

计数器

LaTeX 中的自动编号都借助内部的计数器来完成,计数器包括下面几种:

  • 章节 :part、chapter、section、subsection、subsubsection、paragraph 与 subparagraph。
  • 编号列表 :enumi、enumii、enumiii 与 enumiv。
  • 公式和图表 :equation、figure 与 table。
  • 其他 :page、footnote 与 mpfootnote

  • mpfootnote 命令用于实现 minipage 环境的脚注

使用

通过 \the 接上计数器名称来调用计数器,比如 \thechapter

如果只是输出计数器的数值,可以指定数值的形式,如阿拉伯数字、大小写英文字母,或大小写罗马数字。

常用的命令:

  • \arabic{counter-name}
  • \Alph
  • \alph
  • \Roman
  • \roman
  • % ctex文档类还支持\chinese

图、表格

没看懂

页面设置

借助 geometry 宏包

优秀的讲解

类与对象

表述

类描述了一系列具有相同特征(即数据元素)和行为(即功能方法)的对象
一个上佳的方法是将对象想象成“服务提供者” 。你的程序本身也是为用户提供服务的,它通过使用其他对象提供的服务来做到这一点。所以,你的任务是创建(更好的情况是,从已有的库中找到)一些提供对应服务以解决问题的对象。

内存方面

创建一个类即描述了其对象的外观和行为。直到使用 new 关键字时,你才会真正创建一个对象,以及为该对象分配内存,并使得其方法可以被调用。

消息

调用方法

调用方法的行为被描述为 “==向一个对象发送一条消息==”,如:

1
int x = a.f();
  • f() 代表消息

  • a 代表对象

面向对象编程

面向对象编程描述为“==向对象发送消息==”

接口与类的关系

是抽象与更抽象的关系。

比如,定义人这个类,但是在人之外还有各种动物,或者说,人就是高级动物。但是有一个问题是,人有学历之类的属性,而其他动物一定要有学历这一属性吗?答案是否。人有自己的行为动作,那么其他动物是否也有相同或者类似的行为动作呢?答案是是的。那么我们是不是就可以设计一个动物类,而不添加任何属性呢?那么这个类就可以称作接口了。

引用

引用的作用是关联对象。

引用未必会关联某个对象,如:String s

引用的个人理解

引用就是一个地址或者指针,通过地址来操作相关对象

创建对象

new

使用 new 关键字来创建对象: 我要一个这种类型的对象

1
String s = new String9("asdf");

初始化

数组

  • 放置对象的数组:初始化为 null
  • 放置基本类型的数组:初始化为 0

对象

基本类型 默认值
boolean false
char \u0000(null)
byte (byte)0
short (short)0
int 0
long 0L
float 0.0f
double 0.0d
引用 null
调用引用的方法会==得到异常==,但仍然可以打印一个 null 引用而不会抛出异常

初始化顺序

  1. 自动初始化
  2. 构造初始化

静态数据

  • 字段是基本类型:基本类型的标准初始值
  • 字段是对象引用:默认初始值为 null

初始化引用

  1. 在定义对象时。这意味着它们将始终在调用构造器之前被初始化。
  2. 在该类的构造器中。
  3. 在对象实际使用之前。这通常称为延迟初始化(lazy initialization) 。在对象创建成本高昂且不需要每次都创建的情况下,它可以减少开销。
  4. 使用实例初始化

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// reuse/Bath.java
// 使用组合进行构造器初始化
class Soap {
private String s;

Soap() {
System.out.println("Soap()");
s = "Constructed";
}

@Override
public String toString() {
return s;
}
}

public class Bath {
// 在定义时初始化
private String s1 = "Happy", s2 = "Happy", s3, s4;
private Soap castile;
private int i;
private float toy;

public Bath() {
System.out.println("Inside Bath()");
// 构造初始化
s3 = "Joy";
toy = 3.14f;
castile = new Soap();
}

// 实例初始化
{
i = 47;
}

@Override
public String toString() {
// 延迟初始化
if (s4 == null)
s4 = "Joy";
return "s1 = " + s1 + "\n" + "s2 = " + s2 + "\n" + "s3 = " + s3 + "\n" + "s4 = " + s4 + "\n" + "i = " + i + "\n"
+ "toy = " + toy + "\n" + "castile = " + castile;
}

public static void main(String[] args) {
Bath b = new Bath();
System.out.println(b);
}
}

与C不同点

同名变量

1
2
3
4
5
6
{
int x = 12;
{
int x = 96; // 语法错误
}
}

Java 编译器会提示说,变量 x 已经定义过了。

==类似 C 和 C++ 那样在外围的作用域中“隐藏”变量的方式在 Java 中是不被允许的==

  • 文件中必须存在一个与该文件同名的类(如果你没有这么做,则编译器会报错)

入口

如果你需要创建一个能够独立运行的程序,那么与文件同名的类中还必须包含一个程序启动的入口方法。这个特殊的方法叫作 main(),具有以下的格式和返回值:

1
public static void main(String[] args)

编译

  1. javac <filename>.java
  2. java <filename>

赋值

基本类型

基本类型存储了实际的值,而非只想一个对象的引用,所以在为其赋值的时候,你直接将一个地方的==内容复制到了另一个地方==。

对象赋值

当操作一个对象的时候,我们==真正操作的是这个对象的引用==。当“将一个对象赋值给另一个对象”时,你是将这个引用从一个地方复制到了另一个地方。这意味着对对对象而言,将两个变量同时指向同一个对象。

方法调用中的别名

将一个对象作为参数传递给方法时,也会产生别名

格式化处理

符号 含义
%n 换行
%e 表示以科学记数法显示结果

整数值对象

创建方式

  • 自动转化为 Integer

    通过对 Integer.valueOf() 的==自动调用==来完成的

    1
    2
    int value = 0;
    Integer i = value;
  • 使用标准的对象创建语法 new。 这是以前创建 “包装/装箱” Integer 对象的首选方法

    1
    2
    int value = 0;
    Integer r = new Integer(value);
  • 从 Java 9 开始,valueOf() 优于 [2]。如果尝试在 Java 9 中使用方式 [2],你将收到警告,并被建议使用 [3] 代替。很难确定 [3] 是否的确优于 [1],不过 [1] 看起来更简洁

    1
    2
    int value = 0;
    Integer v = Integer.valueOf(value);
  • 基本类型 int 也可以当作整数值对象使用

    1
    int value = 0;

equal()

  • equals() 方法的默认行为是比较引用。
  • 如果只想比较内容,你必须重写 equals() 方法
  • 大多数标准库会重写 equals() 方法来比较对象的内容而不是它们的引用

boolean

  • 把 int 类型当作 boolean 类型并==不合法==
  • 如果在应该使用字符串的地方使用了布尔值,布尔值会自动转换成合适的文本格式
  • boolean 类型是有限制的:
    • 我们只能赋予它 true 和 false 值,并测试它是真还是假
    • 不能将 boolean 值相加,或对 boolean 值执行其他任何运算

字面值

  • 大写(或小写)的字符 L 表示 long 类型(不过使用小写的 l 容易让人迷惑,因为它看起来就像数字 1)

  • 大写(或小写)的字符 F 表示 float 类型

  • 大写(或小写)的字符 D 表示 double 类型

  • Java 7 引入了==二进制字面量==,通过前缀 0b0B 来表示,它适用于所有整数类型。

  • 可以在数字字面量里使用下划线,这样更易于阅读

    如:341_435_936.445_667

    规则:

    • 只能使用单个下划线,不能连续使用多个;
    • 数字的开头或结尾不能有下划线;
    • FDL 这样的后缀周围不能有下划线;
    • 在二进制或十六进制标识符 bx 的周围不能有下划线。
  • e 表示 “10 的幂次”

    e 大小写都可以,含义相同

  • 编译器一般会将指数作为 double 类型处理,所以如果没有尾部的 f, 我们会收到一条出错提示, 告诉我们必须将 double 类型转换成 float 类型

布尔类型位运算

  • 可以对它执行按位“与” 、按位“或”和按位“异或”运算,但==不能执行按位“非”==
  • 对于布尔值,按位操作符和逻辑操作符具有相同的效果,但它们==不会“短路”==
  • 针对布尔值的按位运算还比逻辑操作符多了一个“异或”运算

移位操作

  • 移位操作符也操纵二进制位,它们==只能用来处理基本类型里的整数类型==。
  • 左移位操作符(==<<==)会将操作符左侧的操作数向左移动,移动的位数在操作符右侧指定(==低位补 0==)
  • “有符号”的右移位操作符(==>>==)则按照操作符右侧指定的位数将操作符左侧的操作数向右移动。“有符号”的右移位操作符使用了“符号扩展” :
    • 如果符号为正,则在==高位插入 0==
    • 否则在==高位插入 1==
  • 种“无符号”的右移位操作符(==>>>==) ,它使用“零扩展”: 无论符号为正还是为负,都在==高位插入 0==
  • 无符号”右移位操作符结合赋值操作符可能会遇到一个问题:如果对 byte 或 short 值进行移位运算,得到的可能不是正确的结果。它们会先被提升为 int 类型,进行右移操作,然后在被赋回给原来的变量时==被截断==, 这时得到==结果是 -1==

字符串操作符 + 和 +=

如果表达式以一个字符串开头,则其后的所有操作数都必须是字符串类型的(编译器会自动把双引号里的字符序列转换成字符串)

字符串转换并不取决于先后顺序

类型转换

自动转换

Java 中无法自动将 int 类型转为 boolean 类型

强制转换

既可以对数值进行类型转换, 也可以对变量进行类型转换

窄化转型

有可能面临信息丢失的危险。窄化转型就是说,将能容纳更多信息的数据类型==转换成无法容纳那么多信息的数据类型==。此时,编译器会要求我们进行强制类型转换,意在提醒我们: “这可能是一个危险的操作,如果的确要这么做,你必须显式地进行类型转换。”

宽化转型

不必显式地进行类型转换, 因为新类型可以容纳比原来的类型更多的信息, 而不会造成任何信息的丢失

截尾与舍入

在执行窄化转型时,会出现截尾与舍入

处理

  • float 或 double 转型为整型值时: 截尾

    java.lang.Math 中的 round(): 舍入

提升

  • 如果对小于 int 类型的基本数据类型(即 char、byte 或者 short)执行算术运算或按位运算,运算执行前这些值就会被自动提升为 int,结果也是 int 类型。
  • 如果要把结果赋值给较小的类型,就==必须使用强制类型转换==(由于把值赋给了较小的类型,可能会出现信息丢失) 。
  • 表达式里出现的最大的数据类型决定了表达式最终结果的数据类型

不允许转换

  • boolean

    Java 可以把任何基本类型转换成别的基本类型,但 boolean 除外,它不允许进行任何类型的转换处理。

  • “类”类型(class type)也不允许进行类型转换

    将一种类型转换成另一种类型需要采用特殊的方法:对象可以在它的类型所属族群里进行类型转换,但不能把它转换成外部的类型。

Java 没有 sizeof()

逗号操作符

逗号操作符不是 ==逗号分隔符==

  • 逗号分隔符:分割生命和函数的不同参数

  • 逗号操作符:Java 里==唯一==用到逗号操作符的地方就是 for 循环的控制表达式。

    • 使用地方:初始化和步进部分,这些语句会按先后顺序执行
    • 注意:可以在for语句里定义多个变量,但它们必须是 ==相同的类型==

循环

for-in

用于数组和容器,也叫做 foreach 语法或 for-in 语法

说明

for-in 会==自动为你生成每一项元素==,这样你就不需要创建 int 变量来对这个元素构成的序列进行计数。

使用

任何反悔了数组的方法都可以使用 for-in

range()

range() 方法可以让我们在更多的地方使用 for-in 语法,从而提高了可读性

总结

不仅方便代码的编写,更重要的是让代码更容易阅读,它表明了你打算做什么(获取数组中的每一个元素) , 作者更提倡使用 for-in 语法

无限循环

  • for(;;)
  • while(true)

goto

Java中没有goto

标签

与goto使用了相同的机制

说明

标签是以冒号结尾的标识符

1
label:

用法

在 Java 中,放置标签的==唯一地方==是正好在迭代语句之前。 “正好”的意思就是,==不要在标签和迭代之间插入任何语句==

break 和 continue 通常只会中断当前循环,但和标签一起用时,它们可以中断这个嵌套的循环,直接跳转到标签所在的位置

注意

  • 使用标签的==唯一理由==就是你用到了嵌套循环,而且你需要使用 break 或 continue 来跳出多层的嵌套。

switch

语法

1
2
3
4
5
6
7
8
9
switch(integral-selector) {
case integral-value1 : statement; break;
case integral-value2 : statement; break;
case integral-value3 : statement; break;
case integral-value4 : statement; break;
case integral-value5 : statement; break;
// ...
default: statement;
}
  • 整数选择器(integral-selector)是一个能生成整数值的表达式

    • int

    • char

    • 字符串

    • enum

初始化

静态块

Java 允许在一个类里将多个静态初始化语句放在一个特殊的“静态子句”里(有时称为静态块)

1
2
3
4
5
6
public class Spoon {
static int i;
static {
i = 47;
}
}

只执行一次:第一次创建该类的对象时,或第一次访问该类的静态成员时(即使从未创建过该类的对象) 。

实例初始化

Java 提供了一种称为实例初始化(instance initialization)的类似语法,==用于初始化每个对象的非静态变量==。

可以用来保证无论调用哪个显式的构造器,某些操作都会发生

实例初始化子句在==构造器之前执行==

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Mugs {
Mug mug1;
Mug mug2;

// 实例初始化
{
mug1 = new Mug(1);
mug2 = new Mug(2);
}

Mugs() {
System.out.println("Mugs()");
}

Mugs(int i) {
System.out.println("Mugs(int)");
}
}

数组

定义

  • 方法1

    1
    int[] a1;
  • 方法2

    1
    int a1[];

注意

  • 编译器不允许指定数组的大小

  • 所拥有的只是对数组的引用(你已经为该引用分配了足够的存储空间) ,并没有为数组对象本身分配任何空间

  • 创建了一个非基本类型数组,其实就是创建了一个引用数组

    即便使用了 new 来创建数组之后,它还只是一个引用数组,直到通过自动装箱为数组里的每个引用本身初始化了一个Integer 对象之后,这个数组的初始化才真正结束

初始化

特殊的初始化方式

只能在创建数组的地方出现,编译器负责存储的分配(相当于使用 new)

1
int[] a1 = {1, 2, 3, 4, 5}

默认

自动初始化为空值(对于数值类型和 char 是 0, 对于 boolean 则是 false)

花括号包围列表

使用花括号保卫列表来初始化对象数组

  • 方法1:局限性更大,它只能用在定义数组的时候

    1
    Integer[] a = {1,  2, 3, };
  • 方法2: 在任何地方使用第二种形式(定义了 b) ,甚至在方法调用中也可以

    1
    Integer[] b = new Integer[]{ 1,  2, 3,  };

注意

  • 初始值列表的最后一个逗号都是可选的(此功能可以让维护长列表更容易)

运算

赋值

可以将一个数组赋值给另一个数组

1
a2 = a1

其实真正做的知识复制了一个引用

查询数组有多少个元素

成员变量: length

可变参数列表

如果列表中没有任何内容,则它会转变成一个大小为零的数组

函数

printBinaryInt() 和 printBinaryLong()

分别接受 int 类型和 long 类型的参数,然后输出其二进制格式

%n

Java 用 %n 来实现的功能,它会根据程序运行的平台生成合适的换行符,不过这仅会在使用 System.out.printf()System.out.format() 时起作用。对于 System.out.println(),你仍然必须使用 \n;如果使用了 %n, println() 只会输出 %n 而不是将其当作换行符。

toBinaryString()

使用 Integer 和 Long 类的静态方法 toBinaryString() 可以很容易实现获取二进制

如果将比较小的类型传递给Integer.toBinaryString() 方法,则该类型会自动被转换为 int 类型

Integer.toString()

1
2
// Integer.toString() 的简化版:
System.out.println("" + x);

Array.toString(a)

1
2
int[] a = new int[10];
System.out.println(Array.toString(a));

getClass()

生成一个对象的类,当打印这个类时,你会看到一个表示该类类型的编码字符串

[ 表示这是后面紧随的类型的数组

ordinal()

表示特定 enum 常量的声明顺序

一个静态的values() 方法

它按照声明顺序生成一个 enum 常量值的数组

自动类型推断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// housekeeping/TypeInference.java 
// {NewFeature} 从 JDK 11 开始
class Plumbus {
}

public class TypeInference {
void method() {
// 显式类型:
String hello1 = "Hello";
// 类型推断:
var hello = "Hello!";
// 用户定义的类型也起作用:
Plumbus pb1 = new Plumbus();
var pb2 = new Plumbus();
}

// 静态方法里也可以启用:
static void staticMethod() {
var hello = "Hello!";
var pb2 = new Plumbus();
}
}

class NoInference {
String field1 = "Field initialization";
// var field2 = "Can't do this";

// void method() {
// var noInitializer; // No inference data
// var aNull = null; // No inference data //
// }

// var inferReturnType() {
// return "Can't infer return type"; //
// }
}

static

static 修饰的字段或方法(也称“类数据 class data” 和 “类方法class method”),只服务于类,而非特定的对象。这些数据或方法只会占用同一块内存空间,即会被由该类创建所有的对象所共享。

注意

调用 static 方法并不依赖于是否提前创建对象。正因为如此,我们也==不能在没有具体对象的情况下, 使用 static 方法直接调用非 static 成员或方法==(因为非 static 成员和方法必须基于特定的对象才能 运行) 。

调用方法

  • 通过对象调用: <对象>.<static 成员>

  • 通过类名调用: <类名>.<static 成员> (==推荐使用该方法==)

    注意:==非 static 成员则不能这样使用==

生成方式

  • static 字段

    • 是基于类创建的
    • 即便你没有创建对象,也可以调用该方法
  • 非 static 字段

    • 是基于对象创建的

见下

访问控制

  • 每个编译单元(文件)都只能有一个public类,但可以根据需要拥有任意数量的包访问权限的类
    • public 类的名称必须与包含编译单元的文件名完全匹配,包括大小写
    • 编译单元里可以没有 public 类。这时你可以随意命名文件
  • 类访问权限, 只有两种选择: 包访问权限和 public
    • 若防止对该类的访问,可以将其所有的构造器都设为 private

Math

函数 功能
random() 生成一个范围为0~1(包括 0,但不包括 1)的 double 值

java.lang.Character 包装器类

函数 功能
isLowerCase() 检测出相关字符是否为小写字母

问题

包与库的关系

不清楚

toString()

每个非基本类型的对象都有一个 toString() 方法

它会在一些特殊情况下被调用,比如当编译器需要一个字符串,但有的是一个对象时。

注解

注解 @Override 用在 toString() 方法上

@Override 是可选的

final

final 数据

  • 定义常量

空白 final

final 执行赋值的操作只能发生在两个地方

  • 在字段定义处使用表达式进行赋值
  • 在每个构造器中

final 参数

在方法内部不能更改参数引用指向的内容

可以读取这个参数, 但不能修改它

final 方法

原因

  • 在方法上放置一个“锁” ,这样就可以防止继承类通过重写来改变该方法的含义
  • 效率:编译器可以将任何对该方法的调用转换为内联调用 (现在可以不用考虑这个因素了,主要考虑 “锁” 的因素)

final 和 private

类中的任何 private 方法都是隐式的 final

final 类

将整个类定义为 final 时(通过在其定义前加上 final 关键字) ,就阻止了该类的所有继承

综述

提升学习能力

学习资料

社会的进步是一部信息存储和传播方式变革的历史

时间不变信息爆炸的应对策略

  • 放弃治疗
  • 加大投入
  • ==提升效率==

学习过程

提升科研能力

什么是科研

  • 解决未知的问题
  • 发现新事物或新规律
  • 创造新事物

科研特性

  • 只认第一,不认第二
  • 科学研究需要探索未知,面对新事物(恐惧感)
  • 需要解决新问题
  • 科研需要大量的重复
  • 科研会经历很多次失败

读研意义

  • 全球视野,以及发现需求的眼光
  • 面对未知事务的勇气,以及愈挫愈勇的韧性
  • 解决问题的English
  • 在持续努力和练习的过程中形成习惯
  • 任何成功的人,都是优秀的研究者

好的选题从哪里来

  • 知己知彼:你对一个领域的了解程度会直接影响你的选题
  • 现实需求:真正来自现实生活的需求最有生命力(洞察力)
  • 长期思考:有的问题需要多年的斯奎
  • 宽广胸怀:一个人的出发点直接决定了选题可能得高度
  • 好的选题非一日之功

提升创新能力

==新?==

==创新?==: 省时省钱爽

学习策略

专注

不可能什么都去学

要“偏科”

向“牛人”学习

  • 思维方式
  • 一本好书
  • 一门好课
  • 一个好资源

思考什么方式学习最高效

善假于物

投资时间

  • 花一定的时间去学习去提高我们的效率,从而节省我们的时间
  • 辛苦很可能是因为无知,要减少自己的无效时间(这就是==低效==)
  • 最怕还不是低效的行为,而是认为这就是世界的运转方式,而意识不到自己的低效,因此,要有==刀刃向内的勇气==,多问问自己,多查查资料,看看有没有更好更快的方法
  • 两类不知道知识
    • 我知道自己不知道某个知识
    • 我根本不知道自己不知道某个知识
      • 见常人所不能见
      • 多看,多了解
      • 被动
      • 做一个复杂、耗时的事情,要思考有没有方法能够变得更简单(尤其是重复的)

总结

流程化+工具+投资时间

建立分享意识

  • 分享是高效、积极、主动的学习
  • 分享是学习的动力所在(输出带动输入)
  • 分享可以为自己带来更多的机会(怀才不遇)
  • 分享可以建立自己的品牌,扩大人脉资源
  • 分享是对知识的主动传播
  • 分享可以检验需求!身边每个牛叉的人都是积极的分享者

费曼学习法:把别人讲明白

汇总

  • 有所学,有所不学的==专注意识==
  • 学习精华内容的==(牛人)意识==
  • 善假于物的==工具意识==
  • 提升效率的==时间投资==意识
  • 传播知识共同成长的==分享意识==

当一项技能不能形成习惯,就无法给我们的生命带来改变

学习与搜索

学习与搜索

课堂三问

  • ==[过去]==这个工具出现的背景是什么?这个工具是怎么发展的?
  • ==[现在]==如何使用这个工具?对我有何价值?
  • ==[未来]==未来这个工具还会怎样发展?有没有更好的解决方案?

什么是学习

把别人的变成自己的

搜索引擎

什么是需求

  • 需求就是客服种种不便,消灭抱怨
  • 每一个抱怨的背后都隐藏着一个未被满足的需求,每一个需求的背后必然隐藏着一个不可忽视的市场

为什么你愿意花钱

快速学习

读论文方法推荐

  • 思维导图梳理

流程化

  • 减少流程间的切换次数
  • 专注在一件事情上:在相对一定的时间中,保持专注

方法

  1. 信息检索

    思考如何准确检索到与主题密切相关的信息;注意检索式,信息来源,时效性等等

  2. 信息收集

    可以通过为知笔记快速收集信息,(信息同步速度会受网络状态影响)

  3. 信息筛选

    通过星标阅读来整理信息

  4. 信息整理

    结合星标、笔记和思维导图来整理信息

  5. 信息输出

    总结成文字,提交作业

科研入门及十大信息源

规划

什么是科研

略(上有)

科研目的

更加爽

科研从哪里开始

科研从需求开始,需求在哪里?

掌握信息,要对领域有一个比较深的了解

十大信息源

  1. 专利
  2. 会议文献
  3. 期刊
  4. 科技报告
  5. 学位论文
  6. 科技档案
  7. 图书
  8. 产品资料
  9. 标准
  10. 政府出版物

  • 来自产品的方法往往比文献中的方法更加可靠
  • 时间出现顺序从上至下

快速调研

处于不同段位检索的方法

  • 检索与目标要紧密结合
  • 入门阶段:快 (体现快速学习能力)
  • 解决问题阶段:广 和 准 (快速解决问题、广阔思路)
  • 选题阶段要: 广 深 新 (要求高)

如何快速了解导师(实验室)的研究方向?

  • 导师的在研课题的基金申请书
  • 自然基金系统,主动查询摘要
  • 实验室网页
  • 实验室的毕业论文
  • 实验室近年发表的论文
  • 师兄师姐
  • 直接和导师沟通

文献数据库及其利用

问题

去哪里查

如何检索

如何管理

如何阅读

WOS 数据库

新文章 or 学科发展相对较慢的论文的重要性

  • 看使用次数

使用

文献检索的前中后阶段

  • 检索前;检索目的、数据库选择、检索策略
  • 检索中:结果的浏览、分析鱼信息输出
  • 检索后:管理、阅读、整理

论文管理工具

==需求==就是做某件事觉得不爽

==创新==就是解决遇到的不爽

快速定位关键文献

困惑

文献很多

选择重要文献研读之

文献帅选的策略鱼调研目的相关

  • 跟踪进展:
  • 特定参考
  • 全面调研
    • 综述
    • 专著
    • 教材 or 教科书 (宽泛)
    • 专家

获取重要资料

  • 专家推荐
  • 文献检索
  • 文献分析

引文分析软件

下载的数据,文献之间没有关联,那么说明检索是不太合适的。如果检索是合适的,那么这些文献之间会相互引用

高效调研

信息检索

  1. 我在哪里可以检索到信息,如何选择关键词

    • 信息源

      • 十大信息源

        • 会议文献
        • 期刊
        • 科技报告
        • 学位论文
        • 图书
      • 网站搜索

      • 科学新闻:简洁易懂

      • 课题申请指南 (新的前沿)

      • 社群

      • 微信公众号

      • 科研项目数据库

      • 博客

      • 面对面交流

      • 实地考察

      • 内部档案

    • 选词

      • 宽泛检索,逐步凝练;通过网页介绍发现关键词
        • 网页
        • 知网
        • 知网指数分析功能
        • 百度学术
      • 关键词分析
        • endnote
      • histcite分析(作图+cited reference)
    • 判断关键词是否宽泛

      • 根据cited references来判断
      • 根据histcite作图中的关联情况来判断
  2. 如何利用数据库和其他信息源

筛选

二分法

标题 -> 摘要 -> 全文

工具

  • zetero

  • histcite

    链接

  • web of science

    • ==登录时不要使用科学上网==
    • 数据库:==Web of Science 核心合集==
    • 记录内容:==全纪录与引用的参考文献==
  • endnote

pd 配置

共享文件夹

选择 整个 $HOME 目录吧

应用程序

打印机

启动顺序

启动顺序 -> 高级 -> BIOS:EFI 64位

CD/DVD

隔离 linux 与 mac

不建议打开

关闭3d加速

安装

从 Live USB 启动 Arch Linux

为 Arch Linux 创建实时 USB 后,关闭计算机,将 USB 插入其中,然后启动系统。

请记住,在某些情况下,您可能无法从启用安全启动的实时 USB 启动。如果您是这种情况,请先访问 BIOS 并禁用安全启动。

Arch Linux 安装有两种可能的选项——传统模式和 UEFI 模式。UEFI(统一可扩展固件接口)模式更新,大多数现代硬件仅支持 UEFI 安装。因此,本 Arch Linux 安装指南将使用 UEFI 模式。

正确启动到 Arch ISO 后,您应该会看到类似于下图所示的内容。

选择Arch Linux install medium (x86_64, UEFI)选项并点击Enter

安装程序解压并加载 Linux 内核后, 您将自动跳转到具有 root 权限的终端。

验证与 Internet 的连接

首先,检查互联网连接。要检查互联网连接,只需 ping 一个网站,如下例所示。

如果你收到一个错误信息,请检查你的互联网连接或路由器。

对磁盘进行分区

我们 Arch Linux 安装指南的下一步是对硬盘进行分区。如果您不熟悉诸如 fdisk 或之类的分区工具,那么您很可能会在这里遇到最大的麻烦cfdisk。不过别担心,这很容易,你会看到。

首先列出您的磁盘:

1
fdisk -l

出于本指南的目的,我们创建一个虚拟机,其中包含一个由 /dev/sda。更改/dev/sda为您的设备名称。

对于基本分区,我们需要创建以下分区布局:

  • /dev/sda1: EFI 系统分区,大小为 512 MB,FAT32 格式。这为存储引导加载程序和引导所需的其他文件提供了空间。
  • /dev/sda2交换分区,4GB 大小。交换空间用于将虚拟内存扩展到已安装的物理内存 (RAM) 之外,或用于挂起磁盘支持。
  • /dev/sda3Linux分区,剩余可用磁盘空间大小,EXT4格式。这是存储我们的 Arch Linux 操作系统、文件和其他信息的根 (/) 分区。

创建 EFI 系统分区

现在让我们通过cfdisk 对机器硬盘驱动器运行命令来实际开始创建磁盘布局分区表 。

1
cfdisk /dev/sda

选择 GPT 标签类型并点击Enter

然后 从底部菜单中选择 Free Space 并点击 New。您可以使用Tab或 箭头键浏览菜单选项。

以 MB ( 1024M) 为单位键入分区大小,然后按Enter键。

/dev/sda1分区仍然被选中的情况下,从底部菜单中选择Type并选择EFI System分区类型。

现在,您已完成 EFI 系统分区的配置。

创建交换分区

j 键下移选项

现在让我们使用相同的过程创建 Swap 分区。再次选择剩余的Free space和 并点击New

以 GB ( 4G) 为单位键入分区大小,然后按Enter键。

/dev/sda2分区仍然被选中的情况下,从底部菜单中选择Type并选择Linux swap分区类型。

现在,您已经完成了 Swap 分区的配置。

创建根分区

最后,您需要创建根 ( /) 分区。再次选择剩余的Free space并点击New

对于 ( /) 大小,保留默认大小值。这意味着,所有剩余的可用空间。按Enter键。

/dev/sda3分区仍然被选中的情况下,从底部菜单中选择Type并选择Linux filesystem分区类型。

现在,您已经完成了根分区的配置。

将更改写入磁盘

接下来,您需要保存所做的更改。选择Write从底部菜单和命中Enter

键入yes并按下Enter键。

我们到此结束。选择Quit并按下Enter即可。

创建文件系统

现在您已准备好磁盘分区,是时候在其上创建文件系统了。但是让我们首先通过运行来查看分区表摘要:

1
fdisk -l

/dev/sda磁盘应该有三个分区(/dev/sda1dev/sda2,和/dev/sda3)类似于上面所示的那些。

前面步骤中分区的创建只是在硬盘提供的存储空间上划出边界,并指定了每条边界线之间的空间类型。现在,是时候用所需的文件系统格式化分区了。

我们必须在这里创建 3 个文件系统,所以让我们开始吧。

对于 EFI 分区类型,创建一个 FAT32 文件系统。

1
mkfs.fat -F32 /dev/sda1

准备交换分区:

1
2
mkswap /dev/sda2
swapon /dev/sda2

对于根分区,创建一个 ext4 文件系统:

1
mkfs.ext4 /dev/sda3

安装 Arch Linux

修改镜像源

进入/etc/pacman.d目录,修改mirrorlist文件,默认情况下,mirrorlist文件里面包含了许多源地址,这样会导致下载程序包速度很慢,我们只需要启用中国的源地址就可以:

1
2
3
cd /etc/pacman.d
cp mirrorlist mirrorlist.backup
vim mirrorlist

修改镜像源

  • 全部下载, 打开China

    1
    2
    3
    # curl -o /etc/pacman.d/mirrorlist https://archlinux.org/mirrorlist/all/
    # vim /etc/pacman.d/mirrorlist
    # 找到 China, 删除 China 下的列表中的 '#'
  • 只下载China的

    1
    # curl -o /etc/pacman.d/mirrorlist https://archlinux.org/mirrorlist/\?country\=CN

同步 pacman 存储库

首先同步 pacman 存储库,以便您可以下载和安装软件:

1
pacman -Syy

挂载根分区 & 安装

我们必须先将根分区 ( /dev/sda3)挂载到 /mnt目录中,然后才能执行所有的安装。

1
mount /dev/sda3 /mnt

安装根分区后,是时候安装所有必要的包了。使用该 pacstrap 命令安装 Arch Linux 所需的软件包。

1
pacstrap /mnt base base-devel linux linux-firmware sudo nano net-tools

下载和安装这些软件包需要一些时间。现在我们可以开始配置我们的系统了。

注: 若不放心,可以多运行几遍 pacstrap /mnt base linux linux-firmware sudo nano

配置已安装的 Arch 系统

fstab文件

生成一下fstab文件,让系统明白自己是谁和自己在哪。

1
genfstab -U /mnt >> /mnt/etc/fstab

然后可以执行以下命令看看fstab文件是否生成正确。

1
cat /mnt/etc/fstab

arch-chroot

以root身份进入我们的新系统,这次是进来是为了配置系统的。

1
arch-chroot /mnt

先告诉系统现在的时间是基于哪个时区的。

1
2
3
4
5
# 设定时区
ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime

# 设置硬件时钟
hwclock -w

设置语言

先安装个vim,当然也可以装nano,看哪个用得顺手。

1
2
3
4
5
# vim
pacman -S vim

# nano
pacman -S nano

编辑/etc/locale.gen

1
2
3
4
5
# vim 方式
vim /etc/locale.gen

# nano方式
nano /etc/locale.gen

把以下几行开头的#符号去掉。

1
2
3
en_US.UTF-8 UTF-8
en_GB.UTF-8 UTF-8 // 这行看是否需要
zh_CN.UTF-8 UTF-8

编辑完保存退出,然后执行下面的指令。

1
locale-gen

现在来编辑一下local.conf

1
2
3
4
5
6
7
8
# vim 方式
vim /etc/locale.conf

# nano 方式
nano /etc/locale.conf

##### 编辑以下内容后保存退出
LANG=en_US.UTF-8

配置网络

创建编辑/etc/hostname,自己定义一个主机名,例如:

1
localhost

然后编辑/etc/hosts,内容如下:

1
2
3
127.0.0.1    localhost
::1 localhost
127.0.1.1 localhost

执行以下命令去应用网络服务自启动:

1
2
systemctl enable systemd-networkd
systemctl enable systemd-resolved
1
2
3
4
5
6
7
pacman -S dhcpcd networkmanager

systemctl enable dhcpcd
systemctl enable NetworkManager

使用无线网络的话,则需安装以下几个软件包(未验证)
pacman -S iw wpa_supplicant dialog (有线网络忽略这行)

配置以太网:**(可选)**

编辑/etc/systemd/network/20-wired.network,内容如下:

1
2
3
4
5
[Match]
Name=ens33

[Network]
DHCP=yes

虽然没啥用,vmware 约等于静态ip了

修改ROOT密码

1
passwd

在 Arch Linux 上安装 GRUB 引导程序

现在我们安装引导加载程序,以便 Arch 在重新启动后启动。Linux 发行版和 Arch Linux 的默认引导加载程序也由 GRUB 包表示。

安装 GRUB 引导加载程序和 EFI 引导管理器包:

1
pacman -S grub efibootmgr os-prober mtools

然后创建挂载点 /dev/sda1并挂载它。

1
2
mkdir /boot/efi
mount /dev/sda1 /boot/efi

现在让我们安装我们的引导加载程序。

1
grub-install –-target=x86_64-efi –-bootloader-id=grub_uefi

输出

1
2
Installing for x86_64-efi platform.
Installation finished. No error reported.

最后,生成 /boot/grub/grub.cfg 文件。

1
grub-mkconfig -o /boot/grub/grub.cfg

创建一个普通用户帐户

用户帐户的创建方法也会自动为用户创建主目录。另外,我们可以给这个账号sudo权限。写比如我们的是Qeuroal

1
useradd -m -G wheel Qeuroal

请务必为新用户设置密码:

1
passwd Qeuroal

接下来,sudo为新创建的用户启用权限:

1
2
3
4
// 使用vim编辑visudo
EDITOR=vim visudo


向下滚动屏幕并找到以下行:

1
2
3
# %wheel ALL=(ALL) ALL
// 在文件中添加如下内容
用户名 ALL=(ALL) ALL

通过删除#符号取消注释。

Arch Linux 安装完成

恭喜!现在我们已经完成了 Arch Linux 的安装,所以我们现在将从终端退出并卸载我们的根分区并重新启动到我们新安装的 Arch Linux 系统

1
2
3
exit
umount -R /mnt
reboot

parallels tools

安装KDE桌面

集显驱动安装

1
2
3
4
5
lspci | grep -e VGA -e 3D	//查看显卡类型

pacman -Ss xf86-video //查询所有开源驱动

sudo pacman -S xf86-video-intel //安装intel集显驱动

安装Xorg

Xorg (简体中文) - ArchWiki (archlinux.org)

1
pacman -S xorg

安装sddm

1
2
3
pacman -S sddm

systemctl enable sddm // 设置开机自启

安装Plasma桌面

  • kde-applications:KDE 的全套应用(软件太多了,很多用不到,在此不安装)
  • konsole:终端
1
pacman -S plasma konsole

重启

1
reboot

How to Install Parallels Tools on Manjaro

Steps required to install Paralells Tools on Manjaro Linux

Steps

  1. Install Manjaro Linux on a VM (it’s recommended to take a snapshot before installing Parallels Tools)

  2. Update the OS

    1
    pacman -Syu
  3. Install the latest LTS kernel and kernel headers (See this)

    Note: To install the headers, run uname -r and look at the first two numbers to figure out the package name. For example if the output of uname -r is 4.19.32-1-MANJARO the kernel headers package name for your kernel would be linux419-headers.

  4. Install base-devel package group and DKMS

    1
    pacman -S base-devel dkms
  5. Connect the Parallels Tools image and run the installer script

    1
    2
    3
    mkdir /mnt/cdrom
    mount /dev/cdrom /mnt/cdrom
    /mnt/cdrom/install

安装

安装 zsh

安装

1
2
3
4
5
6
7
8
# macOS
brew install zsh zsh-completions

# Ubuntu
sudo apt-get install zsh

# CentOS
sudo yum -y install zsh

切换

1
chsh -s /bin/zsh

安装zsh框架

选择其中一个安装即可

安装 oh-my-zsh

1
2
3
4
5
# mac
bash -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"

# ubuntu
sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"

安装 zim

  1. 安装 zim

    1
    curl -fsSL https://raw.githubusercontent.com/zimfw/install/master/install.zsh | zsh
  2. 编辑 Zsh 配置文件 ~/.zimrc

    1
    vim ~/.zimrc
  3. 在文件最后加入下面的一行文字,以添加 powerlevel10k 模块,然后退出。

    1
    zmodule romkatv/powerlevel10k
  4. 安装 powerlevel10k 模块,在终端输入如下命令即可。

    1
    zimfw install

    安装之后,powerlevel10k会让你进行配置。

    在此时配置时,很多图标符号看不到,因为 powerlevel10k 中包含许多特殊图标符号,需要与之兼容的字体。

安装 powerlevel10K 主题

安装主题

方法1 (推荐)

1
git clone --depth=1 https://github.com/romkatv/powerlevel10k.git $ZSH_CUSTOM/themes/powerlevel10k

方法2

1
2
cd ./.oh-my-zsh/themes
git clone https://github.com/romkatv/powerlevel10k.git

配置主题

~/.zshrc 设置如下内容, 以使用p10k主题

1
ZSH_THEME="powerlevel10k/powerlevel10k"

插件

语法高亮插件

1
cd ~/.oh-my-zsh/custom/plugins/ && git clone https://github.com/zsh-users/zsh-syntax-highlighting.git

自动补全

1
cd ~/.oh-my-zsh/custom/plugins/ && git clone https://github.com/zsh-users/zsh-autosuggestions

自动跳转

1
2
3
4
5
# mac
brew install autojump

# ubuntu
sudo apt install autojump

zshrc 配置插件

oh-my-zsh框架

接下来 ~/.zshrc 在插件配置处添加下载的这两个插件名

1
2
3
4
5
6
plugins=(
git
zsh-syntax-highlighting
zsh-autosuggestions
autojump
)

另外, 时间戳改一下格式

1
HIST_STAMPS="yyyy-mm-dd"

zim框架

  • mac

    1
    [[ -s /usr/local/etc/profile.d/autojump.sh  ]] && . /usr/local/etc/profile.d/autojump.sh
  • linux

    • ubuntu

      1
      [[ -s /usr/share/autojump/autojump.sh ]] && . /usr/share/autojump/autojump.sh
    • archlinux

      1
      [[ -s /usr/share/autojump/autojump.bash ]] && . /usr/share/autojump/autojump.bash

      [!ERROR] /usr/share/autojump/autojump.bash:34: command not found: complete
      .zshrc 文件中添加以下命令

      1
      2
      autoload bashcompinit
      bashcompinit

安装依赖字体

运行 p10k configure 向导模式进行p10k的主题定制

完成后会生成一个配置文件 ~/.p10k.zsh,并且在 ~/.zshrc 中自动加入了

下载字体

推荐 JetBrainsMono Nerd Font

网站

  • Nert Fonts 下载

    推荐 JetBrainsMono Nerd Font, 点击下载(v3.0.2)

  • Nert Fonts

  • Nerd Font Github

  • Hack Nerd Font Github

  • Caskaydia Cove Nerd Font

  • MesloLGS NF 官网地址

    1
    2
    3
    4
    5
    mkdir fonts
    curl -o ./fonts/MesloLGS\ NF\ Regular.ttf https://raw.githubusercontent.com/romkatv/powerlevel10k-media/master/MesloLGS%20NF%20Regular.ttf
    curl -o ./fonts/MesloLGS\ NF\ Bold.ttf https://raw.githubusercontent.com/romkatv/powerlevel10k-media/master/MesloLGS%20NF%20Bold.ttf
    curl -o ./fonts/MesloLGS\ NF\ Italic.ttf https://raw.githubusercontent.com/romkatv/powerlevel10k-media/master/MesloLGS%20NF%20Italic.ttf
    curl -o ./fonts/MesloLGS\ NF\ Bold\ Italic.ttf https://raw.githubusercontent.com/romkatv/powerlevel10k-media/master/MesloLGS%20NF%20Bold%20Italic.ttf

安装字体

  1. linux

    1
    bash ./install.sh -of
  2. manually

    1. Unzip the archive (解压)
    2. install the font (安装)
      • macOS
        Select all font files in the folder and ==double-click== the “Install Font” button.
      • Windows
        Select all font files in the folder, right-click any of them, then pick “**==Install==**” from the menu.
      • Linux
        Unpack fonts to ~/.local/share/fonts (or /usr/share/fonts, to install fonts system-wide) and ==fc-cache -f -v==
    3. Restart your software and choose the font

设置字体

Profiles -> Open Profiles -> Edit Profiles -> Text修改字体。红色矩形框勾选,字体其实有好几个,至于用哪个Powerline结尾的都行,只是大小和间距略有不同。

快捷键

快捷键 功能
⌃ + u 清空当前行
⌃ + a 移动到行首
⌃ + e 移动到行尾
⌃ + f 向前移动 (front) 前面
⌃ + b 向后移动 ( before )在某某之前
⌃ + p 上一条命令 ( prev )
⌃ + n 下一条命令 ( next )
⌃ + r 搜索历史命令
⌃ + y 召回最近用命令删除的文字
⌃ + h 删除光标之前的字符
⌃ + d 删除光标所指的字符
⌃ + w 删除光标之前的单词
⌃ + k 删除从光标到行尾的内容
⌃ + t 交换光标和之前的字符
⌘ + Click 可以打开文件,文件夹和链接
⌘ + n 新建窗口
⌘ + t 新建标签页
⌘ + w 关闭当前页
⌘ + 数字&⌘ + 方向键 切换标签页
⌥⌘ + 数字 切换窗口
⌘ + enter 切换全屏
⌘ + d 左右分屏
⇧⌘ + d 上下分屏
⌘ + ; 自动补全历史记录
⇧⌘ + h 自动补全剪贴板历史
⌥⌘ + e 查找所有来定位某个标签页
⌘ + r&⌃ + l 清屏
⌘ + / 显示光标位置
⌥⌘ + b 历史回放
⌘ + f 查找,然后用tab和⇧ + tab可以向右和向左补全,补全之后的内容会被自动复制, 还可以用⌥ + enter将查找结果输入终端

QAs

重要

使用已经写好的脚本安装,github地址

Readme上有使用方法

安装 vimmars

1
2
3
git clone https://github.com/Qeuroal/vimmars.git
cd ~/vimmars
bash ./install.sh

安装 ycm

全部语言的安装

1
2
cd ~/.vim/plugged/YouCompleteMe
python3 ./install.py --all

c/c++

1
2
cd ~/.vim/plugged/YouCompleteMe
python3 ./install.py --clang-completer

推荐几个 Vim 配置方案

推荐另外几个出色的 Vim 教程

图片

自动命令

事件

类别 事件 触发条件
读取 BufNewFile 编辑一个新文件时
读取 BufReadPre 读入新缓冲区之前
读取 BufRead, BufReadPost 读入新缓冲区之后
读取 BufReadCmd 开始编辑新缓冲区之前
读取 FileReadPre 使用:read命令读入文件之前
读取 FileReadPost 使用:read命令读入文件之后
读取 StdinReadPre 由标准输入设备读入缓冲区之前
读取 StdinReadPost 由标准输入设备读入缓冲区之后
写入 BufWrite, BufWritePre 将整个缓冲区写入文件时
写入 BufWritePost 将整个缓冲区写入文件之后
写入 BufWriteCmd 将整个缓冲区写入文件之前
缓冲区 BufAdd, BufCreate 将缓冲区加入缓冲区列表之后
缓冲区 BufDelete 从缓冲区列表中移除缓冲区之前
缓冲区 BufEnter 进入缓冲区之后
缓冲区 BufLeave 离开缓冲区之前
缓冲区 BufWinEnter 在窗口中显示缓冲区之后
缓冲区 BufWinLeave 从窗口中关闭缓冲区之前
缓冲区 BufNew 创建缓冲区之后
缓冲区 BufUnload 卸载缓冲区之前
选项 FileType 设置’filetype’选项之后
选项 Syntax 设置’syntax’选项之后
选项 EncodingChanged ‘encoding’选项改变之后触发命令;
选项 OptionSet 设置任何选项之后
启动退出 VimEnter Vim启动并载入初始化文件之后
启动退出 GUIEnter 启动GUI之后
启动退出 VimLeavePre 改写viminfo文件之前,退出Vim之前
启动退出 VimLeave 改写viminfo文件之后,退出Vim之前
其他 FileChangedShell 当文件的最后修改时间等属性发生改变时
其他 InsertEnter 进入插入模式时
其他 InsertLeave 离开插入模式时
其他 FocusGained Vim成为当前窗口时
其他 FocusLost Vim不再是当前窗口时
其他 WinEnter 进入窗口时
其他 WinLeave 离开窗口时
其他 CursorMoved 在常规模式下移动光标时
其他 CursorMovedI 在插入模式下移动光标时
其他 CursorHold 当超过’updatetime’所指定时间用户没有输入时
其他 vimResized 窗口尺寸变化之后

示例

操作 事件
启动Vim并创建默认窗口 BufWinEnter
创建默认缓冲区 BufEnter
:edit a.txt VimEnter
创建新缓冲区 BufNew
将新缓冲区加入到缓冲区列表之中 BufAdd
退出默认缓冲区 BufLeave
退出默认窗口 BufWinLeave
将默认缓冲区从缓冲区列表之中移除 BufUnload
删除默认缓冲区 BufDelete
将a.txt文件读入新缓冲区 BufReadCmd
激活新缓冲区 BufEnter
激活新窗口 BufWinEnter
进入插入模式 InsertEnter
输入文本 CursorMovedI
退出插入模式 InsertLeave
:wq BufWriteCmd
退出新窗口 BufWinLeave
将新缓冲区从缓冲区列表之中移除 BufUnload
准备退出Vim VimLeavePre
退出Vim VimLeave

命令模式常用快捷键

  • G 移动到文件的最后一行。
  • nG n为数字,移动到文件的第n行。
  • gf   打开以光标所在字符串为文件名的文件
  • gg   移动到文件的第一行。
  • N[Enter]  向下移动N行。
  • h   向左移动光标(
  • j   向下移动光标(
  • k   向上移动光标(
  • l(小L) 向右移动光标(
  • 20j  向下移动20行
  • +Enter  把光标移至下一行第一个非空白字符
  • -  把光标移至上一行第一个非空白字符
  • ^ 把光标移至本行第一个非空白字符
  • 0或Home 移动光标至行首
  • $或End 移动光标至行尾
  • w   向右移动一个单词,光标停在下一个单词开头
  • W 向右移动一个单词,光标移动时忽略一些标点
  • b   向左移动一个单词,光标停在下一个单词开头
  • B   向左移动一个单词,光标移动时忽略一些标点
  • e   向右移动一个单词,光标停在下一个单词末尾
  • E   向右移动一个单词,光标移动时忽略一些标点
  • ge   向左移动一个单词,光标停在下一个单词末尾
  • gE   向左移动一个单词,光标移动时忽略一些标点
  • H   移动光标至屏幕的最上方那一行行首
  • M   移动光标至屏幕的最中间那一行行首
  • L   移动光标至屏幕的最下方那一行行首
  • ( 移动光标至上一句
  • ) 移动光标至下一句
  • { 移动光标至上一段
  • } 移动光标至上一段
  • ctrl+b  屏幕向上移动一页
  • ctrl+f  屏幕向下移动一页
  • ctrl+d  屏幕向上移动半页
  • ctrl+u  屏幕向下移动半页
  • ctrl+e  屏幕向下移动一行
  • n% 到文件n%的位置,如1%,50%, 100%之类
  • zz 将当前行移动到屏幕中央
  • zt 将当前行移动到屏幕顶端
  • zb 将当前行移动到屏幕底端
  • dd   剪切当前行(不是删除,因为此时在缓存内保存了一份剪切的字符串)。
  • d$   删除光标位置到该行最后一个字符(包括光标字元)。
  • d0   删除光标字元至行首字符(不包括光标字元)。
  • dG   删除光标所在行至最后一行所有字符(包括光标所在行)。
  • d1G  删除光标所在行(包括光标所在行)与首行间所有字符。
  • ndd  删除光标所在行(包括)向下的n行。
  • dw   删除光标所在字符至下一个单词间所有字符(包括下一个单词前面的空格)。
  • daw  剪切光标所在位置的单词。
  • das 剪切光标所在位置的句子。
  • x   向后删除一个字符。
  • nx   向后删除n个字符。
  • yy   复制当前行。
  • nyy  复制n行(包含当前行)。
  • y1G  复制第一行至当前行所有字符。
  • yG   复制当前行到最后一行所有字符。
  • y0   复制当前行行首字符至光标字符间所有字符。
  • y$   复制当前行光标字符到行尾字符间所有字符。
  • yaw  复制光标所在位置的单词。
  • yas  复制光标所在位置的句子。
  • ZZ   保存更改并退出。
  • p   将已经复制的数据粘贴到光标的下一行(若是使用y0,y$复制的行的一部分,则粘贴时,会粘贴到光标所在行的光标字符后面)。
  • P   将已经复制的数据粘贴到光标的上一行(若是使用y0,y$复制的行的一部分,则粘贴时,会粘贴到光标所在行的光标字符前面)。
  • u   复原上一操作(撤消)。
  • ctrl+R  重做上一操作。
  • .   点号,重做上一操作。
  • J   将当前行与下一行合并为一行。
  • gJ   将当前行与下一行合并为一行,不过合并后不留空格。
  • << 段落向左缩进一个shiftwidth
  • >> 段落向右缩进一个shiftwidth
  • gq 对选中的长文字进行重排
  • o   当前行下插入空行,并进入插入模式。
  • O   当前行上插入空行,并进入插入模式。
  • i   进入插入模式,从当前光标所在处插入。
  • I   进入插入模式,从当前行第一个非空格处开始插入。
  • a   进入插入模式,从当前光标所在处下一个字符开始插入。
  • A  进入插入模式,从当前光标所在行最后一个字符开始插入。
  • 8i=<ESC> 重复输入小技巧,先按数字8,再按i进入插入模式,再按=或其他字符,如=-,再按Esc退出键,再按一次键,就会出现8=-组成的字符串。如=-=-=-=-=-=-=-=-
  • ESC健  返回命令模式。

编辑模式

在命令模式下按ioa等键可进入插入模式(编辑模式)。

  • o  当前行下插入空行,并进入插入模式。
  • O  当前行上插入空行,并进入插入模式。
  • i  进入插入模式,从当前光标所在处插入。
  • I  进入插入模式,从当前行第一个非空格处开始插入。
  • a  进入插入模式,从当前光标所在处下一个字符开始插入。
  • A  进入插入模式,从当前光标所在行最后一个字符开始插入。

另外按字母s也可以进入插入模式:

  • s  小s,删除光标字符,并进入编辑模式,并在光标所在位置开始编辑。
  • S  大S,删除光标所在行,并进入编辑模式,并在行首开始编辑。
  • ns 删除包含光标字符在内的n个字符,并进入编辑模式。
  • nS 大S,删除光标所在行在内及其后的n-1行,并进入编辑模式。

扩展命令模式常用操作键

  • :r filename 在当前位置插入另一个文件的内容
  • :[n]r filename 在当前文件第n行插入另一个文件的内容
  • :r !date 在光标处插入当前日期与日期,同理,:r !command可以将其他shell命令的输出插入到当前文档
  • :r !cat filename|head -n N 在当前文件光标的下一行插入文件filename的前N行
  • :w 保存
  • :q  离开vim
  • :q!  强制离开
  • :wq  保存后离开
  • :w [filename]    将当前文本另存为filename
  • :n1,n2 w [filename]  将n1至n2行内容另存为filename
  • :ce(nter) 本行文字居中
  • :le(ft) 本行文字靠左
  • :ri(ght) 本行文字靠右
  • /word          向下查找关键字word。
  • ?word          向上查找关键字word。
  • :n1,n2s/word1/word2/g  在第n1行与第n2行之间查找word1字符串,并直接替换为word2。
  • :1,$s/word1/word2/g   在第1行最后一行之间查找word1字符串,并直接替换为word2。
  • :1,$s/word1/word2/gc    在第1行最后一行之间查找word1字符串,并替换为word2(替换时询问用户是否替换)。
  • :.,+5s/^/#/g        将当前行及下面5行标记为注释(包括当前行,共6行)。
  • :6,9 de          删除第6至第9行。
  • :2,8 co 10        复制第2行至第8行内容至第10行后面(从下一行开始)
  • :set nu          显示行号。
  • :set nonu        不显示行号。
  • :set hlsearch        高亮度搜索显示
  • :set nohlsearch      不高亮度搜索显示
  • :set autoindent/noautoindent  自动缩进
  • :set ruler/noruler      设置右下角状态栏说明
  • :set showmode/noshowmode  设置是否显示左下角(-INSERT-)状态列
  • :set backspace=2      =2时,退格键可删除任意字符,=0或=1时只能删除刚输入的字符。
  • :set          显示与系统预设值不同的设定参数。
  • :set all          显示所有的环境参数值
  • :syntax on/off      设置语法高亮
  • :set bg=dark/light      设置背景(background)为暗色/亮色
  • :set tabstop=4       设置Tab宽度
  • :set shiftwidth=4      设置每一级缩进的宽度
  • :Sex 水平分割一个窗口,浏览文件系统
  • :Vex 垂直分割一个窗口,浏览文件系统

vimrc常用环境变量设置

  • set hlsearch    “设置高亮度反白
  • set backspace=2    “可随时用退格键删除
  • set ruler      “可显示最后一行的状态
  • set showmode    “显示状态
  • set nu      “显示行号
  • set bg=dark    “显示不同的底色色调
  • set softtabstop=4 “统一缩进为4
  • set tabstop=4    “设置Tab宽度
  • set shiftwidth=4    “设置每一级缩进的宽度
  • set fileencodings=utf-8,gbk,gb18030,gk2312
  • syntax on      “语法高亮
  • set showcmd    “输入的命令显示出来,看的清楚些
  • set clipboard+=unnamed    “共享剪贴板
  • set cursorline    “突出显示当前行
  • set noeb      “去掉输入错误的提示声音
  • set confirm      “在处理未保存或只读文件的时候,弹出确认
  • set autoindent    “设置自动缩进
  • set cindent “设置自动缩进
  • set expandtab  “用空格代替制表符
  • set smarttab    “在行和段开始处使用制表符
  • set laststatus=2    “总是显示状态栏

~/.vimrc配置文件内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
set hlsearch
set backspace=2
set ruler
set showmode
set nu
set bg=dark
set softtabstop=4
set shiftwidth=4
set fileencodings=utf-8,gbk,gb18030,gk2312
syntax on
set showcmd
set clipboard+=unnamed
set cursorline
set confirm
set autoindent
set cindent
set expandtab
set laststatus=2

可视模式

  • 可视模式(Visual Black),也即区块选择模式,此时可以对列字符进行操作。
  • 在命令模式下,按vVctrl+v可以进入到可视模式。
  • v    进入可视模式,并进行单字符(反白)选择。
  • V    进入可视模式,并进行行(反白)选择。
  • ctrl+v  进入可视模式,并进行区块选择,以长方形的方式(反白)选择字符。
  • y    将反白的地方复制下来。
  • d    删除反白区域。
  • 进入可视模式后,可以按方向键或h/j/k/l(向左、向下、向上、向右)进行反白区域选择。

多视窗功能

  • 输入:sp进入多视窗模式,此时,可以对文本进行前后对照或不同档案间进行对照。
  • :sp 同一文件两个视窗,进行前后对照。
  • :sp [filename] 两个文件进行对照。

在多视窗模式下:

  • ctrl+w 可依次在多个视窗间进行上下循环切换。
  • ctrl+w+k 可依次在多个视窗间进行从下到上切换(先按ctrl+w,松开后,再按k)。
  • ctrl+w+j 可依次在多个视窗间进行从上到下切换(先按ctrl+w,松开后,再按j)。
  • ctrl+w+q 关闭当前视窗下面的视窗(先按ctrl+w,松开后,再按q)。

GDB

GDB(GNU symbolic Debugger)是Linux系统下的强大的调试工具,可以用来调试ada, c, c++, asm, minimal, d, fortran, objective-c, go, java,pascal 等多种语言。

我们以调试go代码为示例来介绍GDB的使用。源码内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import "fmt"

func add(a, b int) int {
sum := 0
sum = a + b
return sum
}
func main() {
sum := add(10, 20)
fmt.Println(sum)
}

构建二进制应用:

1
go build -gcflags="-N -l" -o test main.go

启动调试

1
2
gdb ./test
gdb --args ./test arg1 arg2 # 指定参数启动

进入gdb调试界面之后,执行run命令运行程序。若程序已经运行,我们可以attach该程序的进程id进行调试:

1
2
$ gdb
(gdb) attach 1785

当执行attach命令的时候,GDB首先会在当前工作目录下查找进程的可执行程序,如果没有找到,接着会用源代码文件搜索路径。我们也可以用file命令来加载可执行文件。

或者通过命令设置进程id:

1
2
gdb test 1785 
gdb test --pid 1785

若已运行的进程不含调试信息,我们可以使用同样代码编译出一个带调试信息的版本,然后使用file和attach命令进行运行调试。

1
2
3
4
$ gdb
(gdb) file test
Reading symbols from test...done.
(gdb) attach 1785

可视化窗口

GDB也支持多窗口图形启动运行,一个窗口显示源码信息,一个窗口显示调试信息:

1
gdb test -tui

GDB支持在运行过程中使用Crtl+X+A组合键进入多窗口图形界面, GDB支持的快捷操作有:

1
2
3
Crtl+X+A // 多窗口与单窗口界面切换
Ctrl + X + 2 // 显示两个窗口
Ctrl + X + 1 // 显示一个窗口

运行程序

通过run命令运行程序:

1
(gdb) run

指定命令行参数运行:

1
(gdb) run arg1 arg2

或者通过set命令设置命令行参数:

1
2
(gdb) set args arg1 arg2
(gdb) run

除了run命令外,我们也可以使用start命令运行程序。start命令会在在main函数的第一条语句前面停下来。

1
(gdb) start

断点设置、查看、删除、禁用

设置断点

GDB中是通过break命令来设置断点(BreakPoint),break可以简写成b

  • break function

    在指定函数出设置断点,设置断点后程序会在进入指定函数时停住

  • breshak linenum

    在指定行号处设置断点

  • break +offset/-offset

    在当前行号的前面或后面的offset行处设置断点。offset为自然数

  • break filename:linenum

    在源文件filename的linenum行处设置断点

  • break filename:function

    在源文件filename的function函数的入口处设置断点

  • break \*address

    在程序运行的内存地址处设置断点

  • break

    break命令没有参数时,表示在下一条指令处停住。

  • break … if

    …可以是上述的参数,condition表示条件,在条件成立时停住。比如在循环境体中,可以设置break if i=100,表示当i为100时停住程序

查看断点

我们可以通过info命令查看断点:

1
2
(gdb) info breakpoint # 查看所有断点
(gdb) info breakpoint 3 # 查看3号断点

删除断点

删除断点是通过delete命令删除的,delete命令可以简写成d

1
(gdb) delete 3 # 删除3号断点

断点启用与禁用

1
2
(gdb) disable 3 # 禁用3号断点
(gdb) enable 3 # 启用3号断点

调试

单步执行

next用于单步执行,会一行行执行代码,运到函数时候,不会进入到函数内部,跳过该函数,但会执行该函数,即step over。可以简写成n

1
(gdb) next

单步进入

step用于单步进入执行,跟next命令类似,但是遇到函数时候,会进入到函数内部一步步执行,即step into。可以简写成s

1
(gdb) step

step相关的命令stepi,用于每次执行每次执行一条机器指令。可以简写成si

继续执行到下一个断点

continue命令会继续执行程序,直到再次遇到断点处。可以简写成c:

1
2
(gdb) continue
(gdb) continue 3 # 跳过3个断点

继续运行到指定位置

until命令可以帮助我们实现运行到某一行停住,可以简写成u

1
(gdb) until 5

跳过执行

skip命令可以在step时跳过一些不想关注的函数或者某个文件的代码:

1
2
(gdb) skip function add   # step时跳过add函数
(gdb) info skip # 查看skip列表

其他相关命令:

  • skip delete [num] 删除skip
  • skip enable [num] 启动skip
  • skip disable [num] 关闭skip

注意: 当不带skip号时候,是针对所有skip进行设置。

执行完成当前函数

finish命令用来将当前函数执行完成,并打印函数返回时的堆栈地址、返回值、参数值等信息,即step out

1
(gdb) finish

查看源码

GDB中的list命令用来显示源码信息。list命令可以简写成l

  • list

    从第一行开始显示源码,继续输入list,可列出后面的源码

  • list linenum

    列出linenum行附近的源码

  • list function

    列出函数function的代码

  • list filename:linenum

    列出文件filename文件中,linenum行出的代码

  • list filename:function

    列出文件filename中,函数function的代码

  • list +offset/-offset

    列出在当前行号的前面或后面的offset行附近的代码。offset为自然数。

  • list +/-

    列出当前行后面或者前面的代码

  • list linenum1, linenum2

    列出行linenum1和linenum2之间的代码

查看信息

info命令用来显示信息,可以简写成i

  • info files

    显示当前的debug文件,包含程序入口地址,内存分段布局位置信息等

  • info breakpoints

    显示当前设置的断点列表

  • info registers

    显示当前寄存器的值,可以简写成i r。指定寄存器名称,可以查看具体寄存器信息:i r rsp

    如: 查看 rax 寄存器的信息: info reg rax

  • info all-registers

    显示所有寄存器的值。GDB提供四个标准寄存器:pc是程序计数器寄存器,sp是堆栈指针。fp用于记录当前堆栈帧的指针,ps用于记录处理器状态的寄存器,GDB会处理好不同架构系统寄存器不一致问题,比如对于amd64架构,pc对应就是rip寄存器。

    引用寄存器内容是将寄存器名前置$符作为变量来用。比如$pc就是程序计数器寄存器值。

  • info args

    显示当前函数参数

  • info locals

    显示当前局部变量

  • info frame

    查看当前栈帧的详细信息,包括rip信息,正在运行的指令所在文件位置

  • info variables

    查看程序中的变量符号

  • info functions

    查看程序中的函数符号

  • info functions regexp

    通过正则匹配来查看程序中的函数符号

  • info goroutines

    显示当前执行的goroutine列表,带*的表示当前执行的。注意需要加载go runtime支持。

  • info stack

    查看栈信息

  • info proc mappings

    可以简写成i proc m。用来查看应用内存映射

  • info proc [procid]

    显示进程信息

  • info proc status

    显示进程相关信息,包括user id和group id;进程内有多少线程;虚拟内存的使用;挂起的信号,阻塞的信号,忽略的信号;TTY;消耗的系统和用户时间;堆栈大小;nice值

  • info display

  • info watchpoints

    列出当前所设置了的所有观察点

  • info line [linenum]

    查看第linenum的代码指令地址信息,不带linenum时,显示的是当前位置的指令地址信息

  • info source

    显示此源代码的源代码语言

  • info sources

    显示程序中所有有调试信息的源文件名,一共显示两个列表:一个是其符号信息已经读过的,一个是还未读取过的

  • info types

    显示程序中所有类型符号

  • info types regexp

    通过正则匹配来查看程序中的类型符号

其他类似命令有:

  • show args

    查看命令行参数

  • show environment [envname]

    查看环境变量信息

  • show paths

    查看程序的运行路径

  • whatis var1

    显示变量var1类型

  • ptype var1

    显示变量var1类型,若是var1结构体类型,会显示该结构体定义信息。

查看调用栈

通过where可以查看调用栈信息:

1
2
3
4
5
6
(gdb) where
#0 _rt0_amd64 ()
at /usr/lib/go/src/runtime/asm_amd64.s:15
#1 0x0000000000000001 in ?? ()
#2 0x00007fffffffdd2c in ?? ()
#3 0x0000000000000000 in ?? ()

设置观察点

通过watch命令,可以设置观察点。当观察点的变量发生变化时,程序会停下来。可以简写成wa

1
(gdb) watch sum

查看汇编代码

我们可以通过开启disassemble-next-line自动显示汇编代码。

1
(gdb) set disassemble-next-line on

当面我们可以查看指定函数的汇编代码:

1
(gdb) disassemble main.main

disassemble可以简写成disas。我们也可以将源代码和汇编代码一一映射起来后,查看

1
(gdb) disas /m main.main

GDB默认显示汇编指令格式是AT&T格式,我们可以改成intel格式:

1
(gdb) set disassembly-flavor intel

自动显示变量值

display命令支持自动显示变量值功能。当进行next或者step等调试操作时候,GDB会自动显示display所设置的变量或者地址的值信息。

display命令格式:

1
2
3
display <expr>
display /<fmt> <expr>
display /<fmt> <addr>
  • expr是一个表达式
  • fmt表示显示的格式
  • addr表示内存地址

其他相关命令:

  • undisplay [num]: 不显示
  • delete display [num]: 删除
  • disable display [num]: 关闭自动显示
  • enable display [num]: 开启自动显示
  • info display: 查看display信息

注意: 当不带display号时候,是针对所有display进行设置。

显示将要执行的汇编指令

我们可以通过display命令,实现当程序停止时,查看将要执行的汇编指令:

1
2
(gdb) display /i $pc # 为了让每次执行都能看到汇编代码
(gdb) display /3i $pc # 一次性显示3条指令

==> 指的那一行就是正准备执行的代码, 但还没有执行的

取消显示可以用undisplay命令进行操作。

查看backtrace信息

backtrace命令用来查看栈帧信息。可以简写成bt

1
2
3
4
(gdb) backtrace # 显示当前函数的栈帧以及局部变量信息
(gdb) backtrace full # 显示各个函数的栈帧以及局部变量值
(gdb) backtrace full n # 从内向外显示n个栈桢,及其局部变量
(gdb) backtrace full -n # 从外向内显示n个栈桢,及其局部变量

切换栈帧信息

frame命令可以切换栈帧信息:

1
(gdb) frame n # 其中n是层数,最内层的函数帧为第0帧

其他相关命令:

  • info frame: 查看栈帧列表

调试多线程

GDB中有一组命令能够辅助多线程的调试:

  • info threads

    显示当前可调式的所有线程,线程 ID 前有 “*” 表示当前被调试的线程。

  • thread threadid

    切换线程到线程threadid

  • set scheduler-locking [on|off|step]

    多线程环境下,会存在多个线程运行,这会影响调试某个线程的结果,这个命令可以设置调试的时候多个线程的运行情况,on 表示只有当前调试的线程会继续执行,off 表示不屏蔽任何线程,所有线程都可以执行,step 表示在单步执行时,只有当前线程会执行。

  • thread apply [threadid] [all] args

    对线程列表执行命令。比如通过thread apply all bt full可以查看所有线程的局部变量信息。

查看运行时变量

print命令可以用来查看变量的值。print命令可以简写成pprint命令格式如下:

1
print [</format>] <expr>

format用来设置显示变量的格式,可选的。可用值有如下:

  • x 按十六进制格式显示变量
  • d 按十进制格式显示变量
  • u 按十六进制格式显示无符号整型
  • o 按八进制格式显示变量
  • t 按二进制格式显示变量
  • a 按十六进制格式显示变量
  • c 按字符格式显示变量
  • f 按浮点数格式显示变量
  • z 按十六进制格式显示变量,左侧填充零

expr可以是一个变量,也可以是表达式,也可以是寄存器:

1
2
3
4
5
6
(gdb) p var1 # 打印变量var1
(gdb) p &var1 # 打印变量var1地址
(gdb) p $rsp # 打印rsp寄存器地址
(gdb) p $rsp + 8 # 打印rsp加8后的地址信息
(gdb) p 0xc000068fd0 # 打印0xc000068fd0转换成10进制格式
(gdb) p /x 824634150864 # 打印824634150864转换成16进制格式

print也支持查看连续内存,@操作符用于查看连续内存,@的左边是第一个内存的地址的值,@的右边则想查看内存的长度。

例如对于如下代码:int arr[] = {2, 4, 6, 8, 10};,可以通过如下命令查看arr前三个单元的数据:

1
2
(gdb) p *arr@3
$2 = {2, 4, 6}

查看内存中的值

examine命令用来查看内存地址中的值,可以简写成xexamine命令的语法如下所示:

examine /<n/f/u>

  • n 表示显示字段的长度,也就是说从当前地址向后显示几个地址的内容。
  • f 表示显示的格式
    • d 数字 decimal
    • u 无符号数字 unsigned decimal
    • s 字符串 string
    • c 字符 char
    • u 无符号整数 unsigned integer
    • t 二进制 binary
    • o 八进制格式 octal
    • x 十六进制格式 hex
    • f 浮点数格式 float
    • i 指令 instruction
    • a 地址 address
    • z 十六进制格式,左侧填充零 hex, zero padded on the left
  • u 表示从当前地址往后请求的字节数,默认是4个bytes
    • b 一个字节 byte
    • h 两个字节 halfword
    • w 四个字节 word
    • g 八个字节 giantword

示例:

1
2
3
4
5
6
7
8
9
(gdb) x/10c 0x4005d4 # 打印前10个字符
(gdb) x/16xb a # 以16进制格式打印数组前a16个byte的值
(gdb) x/16ub a # 以无符号10进制格式打印数组a前16个byte的值
(gdb) x/16tb a # 以2进制格式打印数组前16个abyte的值
(gdb) x/16xw a # 以16进制格式打印数组a前16个word(4个byte)的值
(gdb) x $rsp # 打印rsp寄存器执行的地址的值
(gdb) x $rsp + 8 # 打印rsp加8后的地址指向的值
(gdb) x 0xc000068fd0 # 打印内存0xc000068fd0指向的值
(gdb) x/5i schedule # 打印函数schedule前5条指令

修改变量或寄存器值

set命令支持修改变量以及寄存器的值:

1
2
3
(gdb) set var var1=123 # 设置变量var1值为123
(gdb) set var $rax=123 # 设置寄存器值为123
(gdb) set environment envname1=123 # 设置环境变量envname1值为123

查看命令帮助信息

help命令支持查看GDB命令帮助信息。

1
2
(gdb) help status # 查看所有命令使用示例
(gdb) help x # 查看x命令使用帮助

搜索源文件

search命令支持在当前文件中使用正则表达式搜索内容。search等效于forward-search命令,是从当前位置向前搜索,可以简写成foreverse-search命令功能跟forward-search恰好相反,其可以简写成rev

1
2
(gdb) search func add # 从当前位置向前搜索add方法
(gdb) rev func add # 从当前为向后搜索add方法

执行shell命令

我们可以通过shell指令来执行shell命令。

1
2
(gdb) shell cat /proc/27889/maps # 查看进程27889的内存映射。若想查看当前进程id,可以使用info proc命令获取
(gdb) shell ls -alh

GDB对go runtime支持

  • runtime.Breakpoint():触发调试器断点。
  • runtime/debug.PrintStack():显示调试堆栈。
  • log:适合替代 print显示调试信息

进一步阅读

配置环境

安装 Hexo

接下来就需要安装 Hexo 了,这是一个博客框架,Hexo 官方还提供了一个命令行工具,用于快速创建项目、页面、编译、部署 Hexo 博客,所以在这之前我们需要先安装 Hexo 的命令行工具。 命令如下:

1
$ npm install -g hexo-cli

信息提前说明

配置文件说明

在 Hexo 中有两份主要的配置文件,其名称都是 _config.yml。 其中,一份位于*站点根目录下,主要包含 Hexo 本身的配置;另一份位于主题目录*下,这份配置由主题作者提供,主要用于配置主题相关的选项。

为了描述方便,在以下说明中,将前者称为 站点配置文件, 后者称为 主题配置文件

初始化 hexo

  1. 创建文件夹

    1
    $ mkdir io_github_qeuroal
  2. 初始化

    1
    2
    $ hexo init
    $ mkdir ~/Desktop/hexo && ls -al > ~/Desktop/hexo/hexo_init.info
  3. 将 Hexo 编译生成 HTML 代码

    1
    $ hexo generate
  4. 本地运行

    1
    $ hexo server
  5. 站点配置文件

    • 修改根目录下的 _config.yml 文件,找到 Site 区域,这里面可以配置站点标题 title、副标题 subtitle 等内容、关键字 keywords 等内容.

      例:

      1
      2
      3
      4
      5
      6
      # Site
      title: NightTeam
      subtitle: 一个专注技术的组织
      description: 涉猎的主要编程语言为 Python、Rust、C++、Go,领域涵盖爬虫、深度学习、服务研发和对象存储等。
      keywords: "Python, Rust, C++, Go, 爬虫, 深度学习, 服务研发, 对象存储"
      author: NightTeam
    • language 的字段设置为 zh-CN

配置主题

Pure 主题

使用 pure, 教程见这里的 《Hexo搭建个人博客并部署到Github》和《Hexo博客主题pure使用说明》

下载主题

  1. 下载

    1
    $ git clone https://github.com/cofess/hexo-theme-pure.git themes/pure
  2. 启用主题

    修改 站点配置文件

    1
    theme: pure

更新主题 (可选, 暂未使用过)

1
2
$ cd themes/pure
$ git pull

主题配置

导航菜单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 导航菜单
menu:
Home: .
Archives: archives # 归档
Categories: categories # 分类
Tags: tags # 标签
Repository: repository # github repositories
Books: books # 书单
Links: links # 友链
About: about # 关于

# 导航菜单图标(font awesome)
menu_icons:
enable: true # 是否启用菜单图标
home: fa-dashboard
archives: fa-delicious
categories: fa-folder
tags: fa-tags
repository: fa-code
books: fa-leanpub
links: fa-gg
about: fa-coffee

主题配置

1
2
3
4
5
6
7
8
9
10
11
# config
config:
skin: theme-black # 主题颜色 theme-blue theme-green theme-purple
layout: main-center # 布局方式 main-left main-center main-right
toc: true # 是否开启文章章节目录导航
menu_highlight: false # 是否开启当前菜单高亮显示
thumbnail: false # enable posts thumbnail, options: true, false
excerpt_link: Read More
#New
isNewTabLinks: true #是否链接打开新标签页
autoUnfold: true # 默认展开文章目录

页面显示

1
2
3
4
5
6
7
8
9
# Pagination
pagination:
number: false
prev:
alwayShow: true
next:
alwayShow: true
midSize: 2 # 当前页码左右到省略号显示的页码数,默认2,表现为 1 ... 4 5 6 7 8 ... 10
showContent: false # 页面文章小于2篇时显示文章内容

大图显示

1
2
# Fancybox
fancybox: false

捐赠

直接更改照片文件即可

修改为自己的名字

  • cofess<your name>

修改 example.com

全局搜索 example.com 将其替换成网站网址 Qeuroal.top

个人链接

1
2
3
4
5
6
links:
Github: https://github.com/Qeuroal
Blog: http://blog.Qeuroal.com
微博: http://weibo.com/Qeuroal
# 花瓣: http://huaban.com/Qeuroal
# Behance: https://www.behance.net/Qeuroal

个人标签

1
2
3
# My Personal Labels
labels:
- 后端

搜索 (未处理)

主题内置三种站内搜索方式:insight、swiftype、baidu

1
2
3
4
5
# Search
search:
insight: true # you need to install `hexo-generator-json-content` before using Insight Search
swiftype: # enter swiftype install key here
baidu: false # you need to disable other search engines to use Baidu search

文章启用目录索引

1
2
3
4
5
6
title: 文章标题
categories:
- 文章分类
tags:
- 文章标签
toc: true # 是否启用内容索引

分享

支持weibo,qq,qzone,wechat,tencent,douban,diandian,facebook,twitter,google,linkedin

1
2
3
4
5
6
# Share
# weibo,qq,qzone,wechat,tencent,douban,diandian,facebook,twitter,google,linkedin
share:
enable: true # 是否启用分享
sites: weibo,qq,wechat,facebook,twitter # PC端显示的分享图标
mobile_sites: weibo,qq,qzone # 移动端显示的分享图标

分类, 标签, 仓库, about等菜单的配置

/theme/pure/_source 下的所有文件复制到 /source

友情链接

复制theme/pure/_source/ 目录下links文件夹到blog path/source/ 目录下

在 hexo 目录下的 source 文件夹内创建一个名为 _data(禁止改名)的文件夹。

然后在文件内创建一个名为 links.yml 的文件,在其中添加相关数据即可。

单个友情链接的格式为:

1
2
3
4
Name:
link: http://example.com
avatar: http://example.com/avatar.png
desc: "这是一个描述"

添加多个友情链接,我们只需要根据上面的格式重复填写即可。

数学公式

Hexo默认使用 “hexo-renderer-marked” 引擎渲染网页,该引擎会把一些特殊的 markdown 符号转换为相应的 html 标签

解决方案

解决方案有很多,可以网上搜下,为了节省大家的时间,这里只提供亲身测试过的方法。

更换 Hexo 的 markdown 渲染引擎,hexo-renderer-markdown-it-plus 引擎替换默认的渲染引擎 hexo-renderer-marked 即可。

安装hexo-renderer-markdown-it-plus插件
1
2
npm un hexo-renderer-marked --save
npm i hexo-renderer-markdown-it-plus --save
配置

安装插件后,如果未正常渲染LaTeX数学公式,在博客配置文件_config.yml中添加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
markdown_it_plus:
highlight: true
html: true
xhtmlOut: true
breaks: true
langPrefix:
linkify: true
typographer:
quotes: “”‘’
plugins:
- plugin:
name: markdown-it-katex
enable: true
- plugin:
name: markdown-it-mark
enable: false
文章启用mathjax
1
2
title: Hello World
mathjax: true

文章简介内容不全显式

1
showContent: false # 页面文章小于2篇时显示文章内容

删除部分文件

  • themes/pure/_config.yml.example
  • themes/pure/README.cn.md
  • themes/pure/README.md

关闭订阅

  •  # rss
     # rss: /atom.xml
     
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

    - ```yml
    social:
    links:
    github: https://github.com/Qeuroal
    weibo: http://weibo.com/Qeuroal
    twitter: https://twitter.com/Qeuroal
    # facebook: /
    # dribbble: /
    behance: https://www.behance.net/Qeuroal
    # rss: atom.xml

更改头像

1
2
3
profile:
# avatar: images/avatar.jpg
avatar: images/avatar.png

更改个人简介

文件: /source/about/index.md

安装插件

hexo-deployer-git (must, common)

安装
1
$ npm install hexo-deployer-git --save
站点配置(根目录) config.yml

_config.yml

1
2
3
4
5
6
# Deployment
## Docs: https://hexo.io/docs/deployment.html
deploy:
type: git
repo: {git repo ssh address}
branch: master

hexo-generator-feed (normal, 但不推荐订阅)

Github:https://github.com/hexojs/hexo-generator-feed

简介

RSS的生成插件,你可以在配置显示你站点的RSS,文件路径\atom.xml。

安装:
1
$ npm install hexo-generator-feed --save
配置:

在博客配置文件_config.yml中添加

1
2
3
4
5
6
feed:
type: atom
path: atom.xml
limit: 20
hub:
content:
  • type - Feed type. (atom/rss2)
  • path - Feed path. (Default: atom.xml/rss2.xml)
  • limit - Maximum number of posts in the feed (Use 0 or false to show all posts)
  • hub - URL of the PubSubHubbub hubs (Leave it empty if you don’t use it)
  • content - (optional) set to ‘true’ to include the contents of the entire post in the feed.

hexo-generator-json-content (must)

Github

https://github.com/alexbruno/hexo-generator-json-content

简介

用于生成静态站点数据,提供搜索功能的数据源。

安装
1
$ npm install hexo-generator-json-content --save
配置

在博客配置文件_config.yml中添加

1
2
3
4
5
6
7
jsonContent:
ignore:
- path/to/a/page
- url/to/one/post
- an-entire-category
- specific.file
- .ext # a file extension

在github上用hexo搭建了自己的博客,用typora写完一篇博文,满怀欣喜地deploy之后发现图片加载失败…真的很吐血。去网上一搜索,嗯,好像不少人都遇到了这个问题,解决方法看似一大堆实则无效或过时不匹配,绝大多数资料都是安装hexo-asset-image的插件,亲测对于markdown图片路径没有用啊!比出现问题更折磨人的是遇上一堆错误的解决方法…

我想要寻找在本地和网页上都能显示的办法,终于发现了一款插件hexo-image-link,是将markdown图片路径转换为asset_img语法,使得图片能够同时显示在typora和hexo上。

1
{% asset_img "local-image.png" "image file label" %} -> {% asset_img label local-image.png %}
具体步骤
  • 修改_config.yml中的post_asset_folder: true

  • 安装hexo-image-link

    1
    $ npm install hexo-image-link --save
  • 修改 站点配置文件post_asset_folder: true

  • 如果 npm下载比较慢的话,尝试 cnpm下载

  • 安装

    1
    2
    $ npm install -g cnpm --registry=https://registry.npm.taobao.org
    $ cnpm install hexo-image-link --save
  • 修改md文件中的图片路径

hexo-autonofollow (bad)

Github:https://github.com/liuzc/hexo-autonofollow

简介:自动为站外链接添加nofollow属性

安装:

1
$ npm install hexo-autonofollow --save

配置:

在博客配置文件_config.yml中添加

1
2
3
4
5
nofollow:
enable: true
exclude:
- exclude1.com
- exclude2.com
  • enable - 是否启用
  • exclude - 排除域名

配置主题无意义的组件

豆瓣书单 (关闭, 毫无意义)

复制theme/pure/_source/ 目录下books文件夹到blog path/source/ 目录下

1
2
3
4
5
# douban 豆瓣书单
douban:
user: *** # 豆瓣用户名
start: 0 # 从哪一条记录开始
count: 100 # 获取豆瓣书单数据条数
评论 (关闭, 鸡肋)
1
2
comment:
type: # 启用哪种评论系统, 不填即关闭

Next 主题

下载主题

  1. 最新版本:

    Download the Latest Master Branch

  2. 稳定版本

    Download the Latest Release Version

  3. 指定版本

    Download the Specific Release Version

    In rare cases useful, but not recommended.
    You must define version. Let’s take v8.0.0 as an example. Replace it with any version from tags list.

  4. git 下载

    1
    2
    $ cd io_github_qeuroal
    $ git clone https://github.com/next-theme/hexo-theme-next themes/next

教程: Installation

主题配置

修改 theme/next/_config.yml

主题配置文件

1
2
3
4
5
#Schemes
#scheme: Muse
#scheme: Mist
scheme: Pisces
#scheme: Gemini
  1. 将 about、tags、categories 前的 #号去掉,示例如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    menu:
    home: / || fa fa-home
    about: /about/ || fa fa-user
    tags: /tags/ || fa fa-tags
    categories: /categories/ || fa fa-th
    archives: /archives/ || fa fa-archive
    #schedule: /schedule/ || fa fa-calendar
    #sitemap: /sitemap.xml || fa fa-sitemap
    #commonweal: /404/ || fa fa-heartbeat
  2. 新建相关页面

    在博客根目录下执行下列命令

    1
    2
    3
    4
    $ hexo new page "about"
    $ hexo new page "tags"
    $ hexo new page "categories"
    $ hexo new page 404
  3. 修改生成页面的配置

    1
    2
    3
    4
    source/about/index.md
    source/tags/index.md
    source/categories/index.md
    source/404/index.md
  4. 404 相关

    front-matter 中添加 permalink: /404,表示指定该页面固定链接为 http://“主页”/404.html

    1
    2
    3
    4
    5
    6
    7
    ---
    title: 404 Not Found:该页无法显示
    toc: false
    comments: false
    permalink: /404
    ---
    <script type="text/javascript" src="//www.qq.com/404/search_children.js" charset="utf-8" homePageUrl="<%- config.url %>" homePageName="回到我的主页"></script>

设置左上角或右上角 github 图标

主题配置文件,启用 github-banner 如下:

1
2
3
4
github_banner:
enable: true
permalink: https://https://github.com/Qeuroal
title: Follow me on GitHub

设置侧栏阅读进度百分比

编辑站点配置文件,修改 back2top 部分如下

1
2
3
4
copyback2top:
enable: true
sidebar: true
scrollpercent: true

设置网页底部信息

查看主题配置文件,修改 footer 配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
footer:
# Specify the year when the site was setup. If not defined, current year will be used.
#since: 2021

# Icon between year and copyright info.
icon:
# Icon name in Font Awesome. See: https://fontawesome.com/icons
name: fa fa-heart
# If you want to animate the icon, set it to true.
animated: false
# Change the color of icon, using Hex Code.
color: "#ff0000"

# If not defined, `author` from Hexo `_config.yml` will be used.
# Set to `false` to disable the copyright statement.
copyright:

# Powered by Hexo & NexT
powered: true

# Beian ICP and gongan information for Chinese users. See: https://beian.miit.gov.cn, http://www.beian.gov.cn
beian:
enable: false
icp:
# The digit in the num of gongan beian.
gongan_id:
# The full num of gongan beian.
gongan_num:
# The icon for gongan beian. See: http://www.beian.gov.cn/portal/download
gongan_icon_url:

图片懒加载设置

在主题配置文件中启用 lazyload

1
copylazyload: true

设置代码块复制和代码高亮

在主题配置文件中修改 codeblock

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
codeblock:
# Code Highlight theme
# All available themes: https://theme-next.js.org/highlight/
theme:
light: atom-one-dark
dark: stackoverflow-dark
prism:
light: prism
dark: prism-dark
# Add copy button on codeblock
copy_button:
enable: true
# Available values: default | flat | mac
style:
# Fold code block
fold:
enable: false
height: 500

修改文章底部标签样式

在主题配置文件中修改

1
copytag_icon: true

网站icon

1
2
3
4
5
6
favicon:
small: /images/favicon.png
medium: /images/favicon.png
apple_touch_icon: /images/avatar.jpg
safari_pinned_tab: /images/avatar.jpg
#android_manifest: /manifest.json

侧边栏头像

1
2
3
4
5
6
7
8
# Sidebar Avatar
avatar:
# Replace the default image and set the url here.
url: /images/avatar.png
# If true, the avatar will be displayed in circle.
rounded: true
# If true, the avatar will be rotated with the cursor.
rotated: true

侧边栏社交链接

修改主题配置文件

1
2
3
4
5
6
7
8
9
10
social:
GitHub: https://github.com/Qeuroal || fab fa-github
#E-Mail: mailto:yourname@gmail.com || fa fa-envelope
#Weibo: https://weibo.com/yourname || fab fa-weibo
#Twitter: https://twitter.com/yourname || fab fa-twitter
#FB Page: https://www.facebook.com/yourname || fab fa-facebook
#StackOverflow: https://stackoverflow.com/yourname || fab fa-stack-overflow
#YouTube: https://youtube.com/yourname || fab fa-youtube
#Instagram: https://instagram.com/yourname || fab fa-instagram
#Skype: skype:yourname?call|chat || fab fa-skype

在文章底部增加版权信息

编辑 主题配置文件,修改如下配置:

1
2
3
4
5
6
7
8
9
10
creative_commons:
# Available values: by | by-nc | by-nc-nd | by-nc-sa | by-nd | by-sa | cc-zero
license: by-nc-sa
# Available values: big | small
size: small
sidebar: false
post: true
# You can set a language value if you prefer a translated version of CC license, e.g. deed.zh
# CC licenses are available in 39 languages, you can find the specific and correct abbreviation you need on https://creativecommons.org
language:

开启缓存

1
2
3
# Allow to cache content generation. Introduced in NexT v6.0.0.
cache:
enable: true

压缩

1
2
# Remove unnecessary files after hexo generate.
minify: true

侧边栏显示分类条数

1
2
# Posts / Categories / Tags in sidebar.
site_state: true

友链

1
2
3
4
5
6
7
8
9
10
# Blog rolls 友链
links_settings:
icon: fa fa-link # 设置icon
title: 友情链接 # 设置标题
# Available values: block | inline
layout: block # css样式,是一行一条,还是自动换行

links:
友链1: http://yoursite.com
#... 依次追加即可

文章toc

1
2
3
4
5
6
toc:
enable: true # 是否开启
number: true # 自动显示文章编号
wrap: false # 溢出是否换行
expand_all: true # 展开所有还是手风琴
max_depth: 4 # 显示最大深度

顶部元信息

1
2
3
4
5
6
7
post_meta:
item_text: true # 使用中文显示
created_at: true
updated_at:
enable: true
another_day: true
categories: true

首页摘要

1
2
excerpt_description: false  # 关闭自动提取
read_more_btn: trues # 显示阅读全文
  1. 手动控制

    使用<!--more-->控制

  2. 使用 front-matter 中添加 descriptionexcerpt 字段

    1
    2
    3
    4
    ---
    description: your excerpt
    excerpt: your excerpt
    ---

安装插件

hexo-deployer-git (must, common)

  1. 安装

    1
    $ npm install hexo-deployer-git --save
  2. 站点配置(根目录) config.yml

    _config.yml

    1
    2
    3
    4
    5
    6
    # Deployment
    ## Docs: https://hexo.io/docs/deployment.html
    deploy:
    type: git
    repo: {git repo ssh address}
    branch: master

在github上用hexo搭建了自己的博客,用typora写完一篇博文,满怀欣喜地deploy之后发现图片加载失败…真的很吐血。去网上一搜索,嗯,好像不少人都遇到了这个问题,解决方法看似一大堆实则无效或过时不匹配,绝大多数资料都是安装hexo-asset-image的插件,亲测对于markdown图片路径没有用啊!比出现问题更折磨人的是遇上一堆错误的解决方法…

我想要寻找在本地和网页上都能显示的办法,终于发现了一款插件hexo-image-link,是将markdown图片路径转换为asset_img语法,使得图片能够同时显示在typora和hexo上。

1
{% asset_img "local-image.png" "image file label" %} -> {% asset_img label local-image.png %}
具体步骤
  • 修改_config.yml中的post_asset_folder: true

  • 安装hexo-image-link

    1
    $ npm install hexo-image-link --save
  • 修改 站点配置文件post_asset_folder: true

注:

  • 如果 npm下载比较慢的话,尝试 cnpm下载

  • 安装

    1
    2
    $ npm install -g cnpm --registry=https://registry.npm.taobao.org
    $ cnpm install hexo-image-link --save
  • 修改md文件中的图片路径

hexo-generator-searchdb

  1. 安装 exo-generator-searchdb 这个插件

    1
    $ npm install hexo-generator-searchdb --save
  2. 配置 主题配置文件: _config.yml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    # Local Search
    # Dependencies: https://github.com/next-theme/hexo-generator-searchdb
    local_search:
    enable: true
    # If auto, trigger search by changing input.
    # If manual, trigger search by pressing enter key or search button.
    trigger: auto
    # Show top n results per article, show all results by setting to -1
    top_n_per_article: 1
    # Unescape html strings to the readable one.
    unescape: false
    # Preload the search data when the page loads.
    preload: false

部署

  1. 配置站点配置文件 _config.yml

    1
    2
    3
    4
    5
    6
    # Deployment
    ## Docs: https://hexo.io/docs/deployment.html
    deploy:
    type: git
    repo: {git repo ssh address}
    branch: master
  2. 部署

    1
    $ hexo deploy

自定义域名

添加 CNAME 文件

/source 文件夹下添加 CNAME 文件, 如下所示:

hexo 常用命令

1
2
3
4
5
6
7
$ hexo new "postName" # 新建文章
$ hexo new page "pageName" # 新建页面
$ hexo generate # 生成静态页面至public目录
$ hexo server # 开启预览访问端口(默认端口4000,'ctrl + c'关闭server)
$ hexo deploy # 将.deploy目录部署到GitHub
$ hexo help # 查看帮助
$ hexo version # 查看Hexo的版本

git

删除远程仓库里的某个文件/文件夹

在git中可以用git rm命令删除文件(删除远程仓库文件)

1
2
3
4
5
6
git clone 仓库地址
git add .
step1: git rm 文件 //本地中该文件会被删除
step2: git rm -r 文件夹 //删除文件夹
step3: git commit -m '删除某个文件'
step4: git push (origin master)

上面的方法会把对应的本地文件也删除掉,如果不想把本地文件删除,只把缓存区中的对应部分删除,则加上 --cached

1
2
git rm --cached 文件 //本地中该文件不会被删除
git rm -r --cached 文件夹 //删除文件夹

git add . 后面执行上面的命令,再推送到 github 远程仓库上的时候,仓库里面对应的文件/文件夹就会被删除

1
2
3
4
5
6
git clone 仓库地址
git add .
step1: git rm --cached 文件 //本地中该文件不会被删除
step2: git rm -r --cached 文件夹 //删除文件夹
step3: git commit -m '删除某个文件'
step4: git push (origin master)

git commit后,如何撤销commit

修改了本地的代码,然后使用:

1
2
git add file
git commit -m '修改原因'

复制

执行commit后,还没执行push时,想要撤销这次的commit,该怎么办?

解决方案: 使用命令:

1
git reset --soft HEAD^

复制

这样就成功撤销了commit,如果想要连着add也撤销的话,–soft改为–hard(删除工作空间的改动代码)。

1
2
HEAD^ 表示上一个版本,即上一次的commit,也可以写成HEAD~1
如果进行两次的commit,想要都撤回,可以使用HEAD~2

复制

1
2
--soft
不删除工作空间的改动代码 ,撤销commit,不撤销git add file

复制

1
2
--hard
删除工作空间的改动代码,撤销commit且撤销add

复制

另外一点,如果commit注释写错了,先要改一下注释,有其他方法也能实现,如:

1
2
git commit --amend
这时候会进入vim编辑器,修改完成你要的注释后保存即可。

撤销 git add . 并保留修改的方法

执行完 git add .

文件退出暂存区,但是修改保留:

1
git reset --mixed

撤销所有的已经 add 的文件:

1
git reset HEAD .

撤销某个文件或文件夹:

1
git reset HEAD  -filename

另外:可以用 git status Git 会告诉你可以通过那个命令来执行操作。

补充

可以用 git status Git 会告诉你可以通过那个命令来执行操作。

push 失败

case 1

OpenSSL SSL_read: Connection was reset, errno 10054的话就把代理关了

删除未跟踪文件

1
2
# 删除 untracked files
git clean -f
1
2
# 连 untracked 的目录也一起删掉
git clean -fd
1
2
# 连 gitignore 的untrack 文件/目录也一起删掉 (慎用,一般这个是用来删掉编译出来的 .o之类的文件用的)
git clean -xfd
1
2
3
4
# 在用上述 git clean 前,墙裂建议加上 -n 参数来先看看会删掉哪些文件,防止重要文件被误删
git clean -nxfd
git clean -nf
git clean -nfd

多终端同步

新建私有仓库

1
2
3
4
5
6
7
echo "# Readme" >> README.md
git init
git add README.md
git commit -m "first commit"
git branch -M master
git remote add origin <your https repo address>
git push -u origin master

文件夹说明

文件夹 说明 是否需要上传github
node_modules hexo需要的模块,就是一些基础的npm安装模块,比如一些美化插件,在执行npm install的时候会重新生成 不需要
themes 主题文件 ==需要==
public hexo g命令执行后生成的静态页面文件 不需要
packages.json 记录了hexo需要的包的信息,之后换电脑了npm根据这个信息来安装hexo环境 ==需要==
_config.yml 全局配置文件,这个不用多说了吧 ==需要==
.gitignore hexo生成的默认的.gitignore模块 ==需要==
scaffolds 文章的模板 ==需要==
.deploy_git hexo g自动生成的 不需要

.gitignore内容

1
2
3
4
5
6
7
.DS_Store
Thumbs.db
db.json
*.log
node_modules/
public/
.deploy*/

删除.git文件

删除所有主题的 .git 文件,如:rm -rf ./theme/pure/.git

查找 .git 文件

在==根目录==下执行以下命令

1
find . -name ".git*"

上传

1
2
3
git add .
git commit -m "<yout commit statement>"
git push

新电脑上的操作

  1. 下载

    1
    git clone <your repo>
  2. 安装依赖

    1
    npm install

    npm install其实就是读取了 packages.json 里面的信息,自动安装依赖,有的小伙伴可能只执行npm install就行了,不过按照上面的三步是最稳妥的

  3. 仓库更新

    为了保证同步,推荐先合并更新再进行博客的编写

    • pull (不推荐)

      1
      git pull
    • fetch

      1
      2
      git fetch --all    #将远程git仓库上最新的内容拉取到本地,将本地库所关联的远程库更新至最新
      git reset --hard origin/master #强制将本地内容指向刚刚同步git云端内容

      reset 对所拉取的文件不做任何处理,此处不用 pull 是因为本地尚有许多文件,使用 pull 会有一些版本冲突,解决起来也麻烦,而本地的文件都是初始化生成的文件,较拉取的库里面的文件而言基本无用,所以直接丢弃。

  4. 部署

    1
    2
    hexo clean && hexo g && hexo s
    hexo d

更新脚本

1

QAs

Error: Cannot find module ‘hexo-util’

  • 方法1

    1. rm -rf node_modules
    2. npm install hexo-util
  • 方法2

    For those who also encounter this issue, please check your NPM version.

    • > 3: Still not work. Please remove node_modules directory and reinstall using npm install.
    • < 3: Please add hexo-util explicitly via npm install –save-dev hexo-util to you site package deps.

    reference: https://github.com/iissnan/hexo-theme-next/issues/1490

基本概念

简介

模型训练5要素

  • 数据:包括数据读取,数据清洗,进行数据划分和数据预处理,比如读取图片如何预处理及数据增强。
  • 模型:包括构建模型模块,组织复杂网络,初始化网络参数,定义网络层。
  • 损失函数:包括创建损失函数,设置损失函数超参数,根据不同任务选择合适的损失函数。
  • 优化器:包括根据梯度使用某种优化器更新参数,管理模型参数,管理多个参数组实现不同学习率,调整学习率。
  • 迭代训练:组织上面 4 个模块进行反复训练。包括观察训练效果,绘制 Loss/Accuracy 曲线,用 TensorBoard 进行可视化分析

张量

概念

张量

官方定义

A tensor is the primary data structure used by neural networks.

官方定义2

A PyTorch Tensor is conceptually identical to a numpy array: a Tensor is an n-dimensional array, and PyTorch provides many functions for operating on these Tensors.

通俗理解

多维数组(Tensors and nd-arrays are the same thing! So tensors are multidimensional arrays or nd-arrays for short.)

Indexes required Computer science Mathematics
n nd-array nd-tensor
  • A scalar is a $0$ dimensional tensor
  • A vector is a $1$ dimensional tensor
  • A matrix is a $2$ dimensional tensor
  • A nd-array is an $n$ dimensional tensor

拓展

We often see this kind of thing where different areas of study use different words for the same concept.

索引

obvious: 访问一个多维数组, 需要几个索引

即维数,或者说在张量中访问一个元素,需要的索引数

A tensor’s rank tells us how many indexes are needed to refer to a specific element within the tensor.

An axis of a tensor is a specific dimension of a tensor.

tensor 属性

torch.dtype

Data type dtype CPU tensor GPU tensor
32-bit floating point torch.float32 torch.FloatTensor torch.cuda.FloatTensor
64-bit floating point torch.float64 torch.DoubleTensor torch.cuda.DoubleTensor
16-bit floating point torch.float16 torch.HalfTensor torch.cuda.HalfTensor
8-bit integer (unsigned) torch.uint8 torch.ByteTensor torch.cuda.ByteTensor
8-bit integer (signed) torch.int8 torch.CharTensor torch.cuda.CharTensor
16-bit integer (signed) torch.int16 torch.ShortTensor torch.cuda.ShortTensor
32-bit integer (signed) torch.int32 torch.IntTensor torch.cuda.IntTensor
64-bit integer (signed) torch.int64 torch.LongTensor torch.cuda.LongTensor

torch.device

类型

  • CPU
  • GPU

指定GPU

1
device = torch.device('cuda:0')

注意

One thing to keep in mind about using multiple devices is that tensor operations between tensors must happen between tensors that ==exists on the same device==.

torch.layout

应该就是步长==?????==

总览

  • data: 被包装的 Tensor。
  • grad: data 的梯度。
  • grad_fn: 创建 Tensor 所使用的 Function,是自动求导的关键,因为根据所记录的函数才能计算出导数。
  • requires_grad: 指示是否需要梯度,并不是所有的张量都需要计算梯度。
  • is_leaf: 指示是否叶子节点(张量),叶子节点的概念在计算图中会用到,后面详细介绍。
  • dtype: 张量的数据类型,如 torch.FloatTensor,torch.cuda.FloatTensor。
  • shape: 张量的形状。如 (64, 3, 224, 224)
  • device: 张量所在设备 (CPU/GPU),GPU 是加速计算的关键

构建tensor的方法

直接构造tensor

  • torch.Tensor(data)
  • torch.tensor(data)
  • torch.as_tensor(data)
  • torch.from_numpy(data)

根据数值构造tensor

  • torch.eye(k): 生成 k 阶的单位矩阵

  • torch.zeros(shape): 生成元素值都为 0, 形状为 shape 的张量

  • torch.zeros_like(input, dtype=None, layout=None, device=None, requires_grad=False, memory_format=torch.preserve_format): 根据 input 形状创建全 0 张量

  • torch.ones(shape): 生成元素值都为 1, 形状为 shape 的张量

  • torch.rand(shape): 生成元素值随机, 形状为 shape 的张量

  • torch.full()

    1
    torch.full(size, fill_value, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False)
    • size: 张量的形状,如 (3,3)
    • fill_value: 张量中每一个元素的值
  • torch.full_like()

    1
    torch.full_like(input, fill_value, dtype=None, layout=torch.strided, device=None, requires_grad=False, memory_format=torch.preserve_format) → Tensor
    • input: the size of input will determine size of the output tensor.
    • fill_value: the number to fill the output tensor with.
  • torch.arange(): 创建等差的 1 维张量。注意区间为 $[start, end)$

    1
    torch.arange(start=0, end, step=1, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False)
    • start: 数列起始值
    • end: 数列结束值,开区间,取不到结束值
    • step: 数列公差,默认为 1
  • torch.linspace() : 创建均分的 1 维张量。数值区间为 $[start, end]$

    1
    torch.linspace(start, end, steps=100, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False)
    • start: 数列起始值
    • end: 数列结束值
    • steps: 数列长度 (元素个数)
  • touch.logspace() : 创建对数均分的 1 维张量, 数值区间为 $[start, end]$, 底为 base

    1
    torch.logspace(start, end, steps=100, base=10.0, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False)
    • start: 数列起始值
    • end: 数列结束值
    • steps: 数列长度 (元素个数)
    • base: 对数函数的底,默认为 10

根据概率构造tensor

  • torch.normal(mean, std, *, generator=None, out=None): 生成正态分布 (高斯分布)

    • mean: 均值
    • std: 标准差

    4种模式

    • mean 为标量, std 为标量, 则==需要设置 size==

      如: torch.normal(0., 1., size=(4,))

    • mean 为标量,std 为张量

    • mean 为张量,std 为标量

    • mean 为张量,std 为张量

  • torch.randn()torch.randn_like(): 生成标准正态分布。

    1
    torch.randn(*size, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False)
    • size: 张量的形状
  • torch.rand()torch.rand_like(): 在区间 $[0, 1)$ 上生成均匀分布

    1
    torch.rand(*size, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False)
  • torch.randint()torch.randint_like(): 在区间 $[low, high)$ 上生成整数均匀分布

    1
    randint(low=0, high, size, *, generator=None, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False)
    • size: 张量的形状
  • torch.randperm() :生成从 0 到 n-1 的随机排列。常用于生成索引。

    1
    torch.randperm(n, out=None, dtype=torch.int64, layout=torch.strided, device=None, requires_grad=False)
    • n: 张量的长度
  • torch.bernoulli()

    1
    torch.bernoulli(input, *, generator=None, out=None)

    功能:以 input 为概率,生成伯努利分布 (0-1 分布,两点分布)

    • input: 概率值

使用data的构造方法的不同

输出不同对比

code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
data = np.array([1, 2, 3], dtype=np.int32)
print("numpy: ", data, "; dtype: ", data.dtype)

# torch.Tensor(data)
tensor1 = torch.Tensor(data)
print("tensor1: ", tensor1, "; dtype: ", tensor1.dtype)

# torch.tensor(data)
tensor2 = torch.tensor(data)
print("tensor2: ", tensor2, "; dtype: ", tensor2.dtype)

# torch.as_tensor(data)
tensor3 = torch.as_tensor(data)
print("tensor3: ", tensor3, "; dtype: ", tensor3.dtype)

# torch.from_numpy(data)
tensor4 = torch.from_numpy(data)
print("tensor4: ", tensor4, "; dtype: ", tensor4.dtype)

output

1
2
3
4
5
numpy:  [1 2 3] ; dtype:  int32
tensor1: tensor([1., 2., 3.]) ; dtype: torch.float32
tensor2: tensor([1, 2, 3], dtype=torch.int32) ; dtype: torch.int32
tensor3: tensor([1, 2, 3], dtype=torch.int32) ; dtype: torch.int32
tensor4: tensor([1, 2, 3], dtype=torch.int32) ; dtype: torch.int32

数据类型对比

torch.Tensor(data)

数据 数据类型
tensor([1., 2., 3.]) torch.float32

torch.tensor(data)

数据 数据类型
tensor([1, 2, 3], dtype=torch.int32) torch.int32

torch.as_tensor(data)

数据 数据类型
tensor([1, 2, 3], dtype=torch.int32) torch.int32

torch.from_numpy(data)

数据 数据类型
tensor([1, 2, 3], dtype=torch.int32) torch.int32

本质的不同

torch.Tensor() 和 torch.tensor()

  • torch.Tensor(data)

    这是构造函数

  • torch.tensor(data)

    • 这是工厂函数

      You can think of the torch.tensor() function as a factory that builds tensors given some parameter inputs.

    • 相比于 torch.Tensor(data) 更好

      the factory function torch.tensor() has better documentation and more configuration options, so it gets the winning spot at the moment.

dtype 的对比

  • ``torch.Tensor()`

    使用的是默认类型

    注意: 这个函数中的dtype也而==不可以==显式的设置, 如: torch.Tensor(data, dtype=torch.int32)

  • other(torch.tensor(), torch.as_tensor(), torch.from_numpy())

    使用的是推断类型(根据传入的原始数据, 自动推断出元素的类型)

    注意: 这些函数中的dtype也而可以显式的设置, 如: torch.tensor(data, dtype=torch.float32)

内存对比: copy vs share

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
data = np.array([1, 2, 3], dtype=np.int32)
print("numpy: ", data, "; dtype: ", data.dtype)

## create tensor data
# torch.Tensor(data)
tensor1 = torch.Tensor(data)
# torch.tensor(data)
tensor2 = torch.tensor(data)
# torch.as_tensor(data)
tensor3 = torch.as_tensor(data)
# torch.from_numpy(data)
tensor4 = torch.from_numpy(data)

## show different
print("old: ")
print("\ttensor1: ", tensor1, "; dtype: ", tensor1.dtype)
print("\ttensor2: ", tensor2, "; dtype: ", tensor2.dtype)
print("\ttensor3: ", tensor3, "; dtype: ", tensor3.dtype)
print("\ttensor4: ", tensor4, "; dtype: ", tensor4.dtype)

# 更改数据
data[0] = 0

print("new:")
print("\ttensor1: ", tensor1, "; dtype: ", tensor1.dtype)
print("\ttensor2: ", tensor2, "; dtype: ", tensor2.dtype)
print("\ttensor3: ", tensor3, "; dtype: ", tensor3.dtype)
print("\ttensor4: ", tensor4, "; dtype: ", tensor4.dtype)

out:

1
2
3
4
5
6
7
8
9
10
old: 
tensor1: tensor([1., 2., 3.]) ; dtype: torch.float32
tensor2: tensor([1, 2, 3], dtype=torch.int32) ; dtype: torch.int32
tensor3: tensor([1, 2, 3], dtype=torch.int32) ; dtype: torch.int32
tensor4: tensor([1, 2, 3], dtype=torch.int32) ; dtype: torch.int32
new:
tensor1: tensor([1., 2., 3.]) ; dtype: torch.float32
tensor2: tensor([1, 2, 3], dtype=torch.int32) ; dtype: torch.int32
tensor3: tensor([0, 2, 3], dtype=torch.int32) ; dtype: torch.int32
tensor4: tensor([0, 2, 3], dtype=torch.int32) ; dtype: torch.int32

总结

Share Data Copy Data
torch.as_tensor() torch.tensor()
torch.from_numpy() torch.Tensor()
  • This sharing just means that the actual data in memory exists in a single place
  • Sharing data is more efficient and uses less memory than copying data because the data is not written to two locations in memory.

torch.as_tensor() 和 torch.from_numpy() 的选择

  • The torch.from_numpy() function only accepts numpy.ndarrays
  • the torch.as_tensor() function accepts a wide variety of array-like objects including other PyTorch tensors

张量操作

拼接

torch.cat()

将张量按照 dim 维度进行拼接

1
torch.cat(tensors, dim=0, out=None)
  • tensors: 张量序列
  • dim: 要拼接的维度

torch.stack()

将张量在新创建的 dim 维度上进行拼接

1
torch.stack(tensors, dim=0, out=None)
  • tensors: 张量序列
  • dim: 要拼接的维度

意义

使用stack可以保留两个信息:[1. 序列] 和 [2. 张量矩阵] 信息,属于【==扩张==再拼接】的函数

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 # 假设是时间步T1
T1 = torch.tensor([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
# 假设是时间步T2
T2 = torch.tensor([[10, 20, 30],
[40, 50, 60],
[70, 80, 90]])

print(torch.stack((T1,T2),dim=0).shape)
print(torch.stack((T1,T2),dim=1).shape)
print(torch.stack((T1,T2),dim=2).shape)
print(torch.stack((T1,T2),dim=3).shape)


#################### outputs: #############
torch.Size([2, 3, 3])
torch.Size([3, 2, 3])
torch.Size([3, 3, 2])
'选择的dim>len(outputs),所以报错'

切分

torch.chunk()

1
torch.chunk(input, chunks, dim=0)

功能:将张量按照维度 dim 进行平均切分。若不能整除,则最后一份张量小于其他张量。

  • input: 要切分的张量
  • chunks: 要切分的份数
  • dim: 要切分的维度

torch.split()

1
torch.split(tensor, split_size_or_sections, dim=0)

功能:将张量按照维度 dim 进行平均切分。可以指定每一个分量的切分长度。

  • tensor: 要切分的张量
  • split_size_or_sections:
    • 为 int 时,表示每一份的长度,如果不能被整除,则最后一份张量小于其他张量;
    • 为 list 时,按照 list 元素作为每一个分量的长度切分。如果 list 元素之和不等于切分维度 (dim) 的值,就会报错。
  • dim: 要切分的维度

索引

torch.index_select()

1
torch.index_select(input, dim, index, out=None)

功能:在维度 dim 上,按照 index 索引取出数据拼接为张量返回。

  • input: 要索引的张量
  • dim: 要索引的维度
  • index: 要索引数据的序号

torch.mask_select()

1
torch.masked_select(input, mask, out=None)

功能:按照 mask 中的 True 进行索引拼接得到一维张量返回。

  • 要索引的张量
  • mask: 与 input 同形状的布尔类型张量

t.le() t.ge()

1
t.le(5)

变换

torch.reshape()

1
torch.reshape(input, shape)

功能:变换张量的形状。当张量在内存中是连续时,返回的张量和原来的张量共享数据内存,改变一个变量时,另一个变量也会被改变。

  • input: 要变换的张量
  • shape: 新张量的形状
    • -1 表示这个维度是根据其他维度计算得出的

torch.transpose()

1
torch.transpose(input, dim0, dim1)

功能:交换张量的两个维度。常用于图像的变换,比如把 $chw$ 变换为 $hwc$。

  • input: 要交换的变量
  • dim0: 要交换的第一个维度
  • dim1: 要交换的第二个维度

torch.t()

功能:2 维张量转置,对于 2 维矩阵而言,等价于torch.transpose(input, 0, 1)

torch.squeeze()

1
torch.squeeze(input, dim=None, out=None)

功能:压缩长度为 1 的维度。

  • dim: 若为 None,则移除所有长度为 1 的维度;若指定维度,则==当且仅当该维度长度为 1 时可以移除==。

torch.unsqueeze()

1
torch.unsqueeze(input, dim)

功能:根据 dim 扩展维度,长度为 1。

数学运算

torch.add()

1
2
torch.add(input, other, out=None)
torch.add(input, other, *, alpha=1, out=None)

功能:逐元素计算 input + alpha * other。因为在深度学习中经常用到先乘后加的操作。

  • input: 第一个张量
  • alpha: 乘项因子
  • other: 第二个张量

torch.addcdiv()

1
torch.addcdiv(input, tensor1, tensor2, *, value=1, out=None)

计算公式: $out_i = input_i + value \times \frac{tensor1_i}{tensor2_i}$

torch.addcmul()

1
torch.addcmul(input, tensor1, tensor2, *, value=1, out=None)

计算公式: $out_i = input_i + value \times tensor1_i \times tensor2_i$

bridge with Numpy

Tensor to numpy array

1
tensor.numpy()

函数

见这里

获取torch默认类型函数

1
torch.get_default_dtype()

suffix “_”

在函数的后面加上后缀 _, 将会改变作用的数据

示例

1
2
3
4
5
6
7
8
9
10
11
# source tensor
tensor = torch.eye(5)
print(tensor)

# tensor after using add()
tensor.add(5)
print(tensor)

# tensor after using add_()
tensor.add_(5)
print(tensor)

out

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
tensor([[1., 0., 0., 0., 0.],
[0., 1., 0., 0., 0.],
[0., 0., 1., 0., 0.],
[0., 0., 0., 1., 0.],
[0., 0., 0., 0., 1.]])
tensor([[1., 0., 0., 0., 0.],
[0., 1., 0., 0., 0.],
[0., 0., 1., 0., 0.],
[0., 0., 0., 1., 0.],
[0., 0., 0., 0., 1.]])
tensor([[6., 5., 5., 5., 5.],
[5., 6., 5., 5., 5.],
[5., 5., 6., 5., 5.],
[5., 5., 5., 6., 5.],
[5., 5., 5., 5., 6.]])

自动求导(autograd)

只要搭建好前向计算图,利用 torch.autograd 自动求导得到所有张量的梯度

torch.autograd.backward()

1
torch.autograd.backward(tensors, grad_tensors=None, retain_graph=None, create_graph=False, grad_variables=None)

功能

  • 自动求取梯度

参数

  • tensors: 用于求导的张量,如 loss

  • retain_graph: 保存计算图。PyTorch 采用动态图机制,默认每次反向传播之后都会释放计算图。这里设置为 True 可以不释放计算图。

    y.backward() 方法调用的是 torch.autograd.backward(self, gradient, retain_graph, create_graph)。但是在第二次执行 y.backward() 时会出错。因为 PyTorch 默认是每次求取梯度之后不保存计算图的,因此第二次求导梯度时,计算图已经不存在了。在第一次求梯度时使用 y.backward(retain_graph=True) 即可。

  • create_graph: 创建导数计算图,用于高阶求导

  • grad_tensors: 多梯度权重。当有多个 loss 混合需要计算梯度时,设置每个 loss 的权重。

retain_grad 参数

反向传播结束之后仍然需要保留非叶子节点的梯度

grad_tensors 参数

给 loss 设置权重

torch.autograd.grad()

1
torch.autograd.grad(outputs, inputs, grad_outputs=None, retain_graph=None, create_graph=False, only_inputs=True, allow_unused=False)

功能

求取梯度

参数

  • outputs: 用于求导的张量,如 loss
  • inputs: 需要梯度的张量
  • create_graph: 创建导数计算图,用于高阶求导
  • retain_graph:保存计算图
  • grad_outputs: 多梯度权重计算

返回值

返回结果是一个 tunple,需要取出第 0 个元素才是真正的梯度。

注意点

  • 在每次反向传播求导时,计算的梯度不会自动清零。如果进行多次迭代计算梯度而没有清零,那么梯度会在前一次的基础上叠加。

    故使用 w.grad.zero() 将梯度清零

  • 依赖与叶子节点的节点, requires_grad 属性默认为 True

  • 叶子节点不可以执行 inplace 操作

    1
    2
    3
    4
    5
    6
    7
    ## inplace 操作: 改变后的值和原来的值内存地址是同一个
    a += x
    a.add_(x)

    ## 非inplace 操作: 改变后的值和原来的值内存地址不是同一个
    a = a + x
    a.add(x)

    举例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    print("非 inplace 操作")
    a = torch.ones((1, ))
    print(id(a), a)
    # 非 inplace 操作,内存地址不一样
    a = a + torch.ones((1, ))
    print(id(a), a)

    print("inplace 操作")
    a = torch.ones((1, ))
    print(id(a), a)
    # inplace 操作,内存地址一样
    a += torch.ones((1, ))
    print(id(a), a)

    结果自己跑一下嘛

    问题

    如果在反向传播之前 inplace 改变了叶子的值, 再执行 backward() 会报错

逻辑回归

概念

二分类模型

模型表达式 $y=f(z)=\frac{1}{1+e^{-z}}$,其中 $z=WX+b$。$f(z)$ 称为 sigmoid 函数,也被称为 Logistic 函数

分类原则

逻辑回归是在线性回归的基础上加入了一个 sigmoid 函数,这是为了更好地描述置信度,把输入映射到 (0,1) 区间中,符合概率取值。

训练步骤

  1. 导入模型

  2. 计算误差 loss

  3. 后向传播 (call loss.backward())

  4. 加载优化器 optimizer: 注册模型中的所有参数

  5. 梯度下降 (call optim.step())

    各参数的梯度保存在 .grad 属性中

反向传播

To backpropagate the error all we have to do is to loss.backward(). You need to clear the existing gradients though, ==else gradients will be accumulated to existing gradients.==

模型构建总结

步骤

  1. 加载数据
  2. 定义神经网络
  3. 定义损失函数
  4. 训练网络
  5. 测试网络

PyTorch 构建模型需要 5 大步骤:

  • 数据:包括数据读取,数据清洗,进行数据划分和数据预处理,比如读取图片如何预处理及数据增强。
  • 模型:包括构建模型模块,组织复杂网络,初始化网络参数,定义网络层。
  • 损失函数:包括创建损失函数,设置损失函数超参数,根据不同任务选择合适的损失函数。
  • 优化器:包括根据梯度使用某种优化器更新参数,管理模型参数,管理多个参数组实现不同学习率,调整学习率。
  • 迭代训练:组织上面 4 个模块进行反复训练。包括观察训练效果,绘制 Loss/ Accuracy 曲线,用 TensorBoard 进行可视化分析。

数据

数据模块

细分

  • 数据收集: 样本和标签
  • 数据划分: 训练集, 验证集和测试集
  • 数据读取:对应于PyTorch 的 DataLoader。其中 DataLoader 包括 Sampler 和 DataSet。Sampler 的功能是生成索引, DataSet 是根据生成的索引读取样本以及标签。
  • 数据预处理:对应于 PyTorch 的 transforms

考虑

  • Who created the dataset?
  • How was the dataset created?
  • What transformations were used?
  • What intent does the dataset have?
  • Possible unintentional consequences?
  • Is the dataset biased?
  • Are there ethical issues with the dataset?

DataLoader

torch.utils.data.DataLoader()

1
torch.utils.data.DataLoader(dataset, batch_size=1, shuffle=False, sampler=None, batch_sampler=None, num_workers=0, collate_fn=None, pin_memory=False, drop_last=False, timeout=0, worker_init_fn=None, multiprocessing_context=None)

功能

构建可迭代的装载器

参数

  • dataset: Dataset 类,决定数据从哪里读取以及如何读取
  • batchsize: 批大小
  • num_works: 是否多进程读取数据
  • sheuffle: 每个 epoch 是否乱序
  • drop_last: 当样本数不能被 batchsize 整除时,是否舍弃最后一批数据

Epoch, I’t’eration, Batchsize

  • Epoch: ==所有训练样本==都已经输入到模型中,称为一个 Epoch
  • Iteration: ==一批样本==输入到模型中,称为一个 Iteration
  • Batchsize: ==批大小==,决定一个 iteration 有多少样本,也决定了一个 Epoch 有多少个 Iteration

举例: 假设样本总数有 80,设置 Batchsize 为 8,则共有 $80 \div 8=10$ 个 Iteration。这里 $1 Epoch = 10 Iteration$。

torch.utils.data.Dataset

功能

Dataset 是抽象类,所有自定义的 Dataset 都需要继承该类,并且重写__getitem()__方法和__len__()方法

  • __getitem()__方法的作用是接收一个索引,返回索引对应的样本和标签,这是我们自己需要实现的逻辑
  • __len__()方法是返回所有样本的数量

数据读取

  • 读取哪些数据:每个 Iteration 读取一个 Batchsize 大小的数据,每个 Iteration 应该读取哪些数据。
  • 从哪里读取数据:如何找到硬盘中的数据,应该在哪里设置文件路径参数
  • 如何读取数据:不同的文件需要使用不同的读取方法和库。

步骤

  1. 划分数据集为: 训练集, 验证集和测试集, 比例为 $8 : 1 : 1$, 并构造路径
  2. 实现 get_img_info()__getitem__(self, index), __len__() 函数
  3. 构建模型

DataSet

MNIST 数据集

损失函数

均方误差 MSE

$MSE = \frac{1}{m}\sum_{i = 1}^{m}{(y_i-\hat{y_i})^2}$

  • $y_i$ 是预测值
  • $\hat{y_i}$ 是真实值

学习链接

常用变量

img

1
img = cv2.imread(filename)

图片相关

读图:imread

定义

1
imread(<文件名>, flag)

imread 第二个参数: flag

imread 第二个参数 含义
cv2.IMREAD_COLOR 缺省方式,读取图像为 BGR 8-bit 格式.
cv2.IMREAD_UNCHANGED 图像格式不做任何改变,可用在读取带 alpha 通道的图片
cv2.IMREAD_GRAYSCALE 读取图像为转换为灰度图

显示图:imshow

定义

1
imshow(<窗口名称>, < 图像实例 >)

waitkey

waitKey() 传入的参数如果为 0,会无限等待直到任何按键按下,或者传入其他数值参数表示等待时长,单位为 ms,时长结束后显示图像窗口会关闭。

参数 作用
0 无限等待直到任何按键按下
传入其他数值参数 表示等待时长 (单位: ms),时长结束后显示图像窗口会关闭。

返回值

返回: 键入的值

使用: 检查按键,键入 q or Q 退出

1
2
3
key = waitKey(20) & 0xff
if (key == ord('q') or key == ord('Q')):
break

waityKey() 返回的数值和 0xff 相与后再和字符的 ord() 值比较,是为了规避某些系统中 waitKey() 返回的数值在高字节为非 0 值的情况。

调整窗口

使用 imshow() 方法显示图像时,默认是以图像的像素大小显示的,可以通过 nameWindow(窗口名称, cv2.WINDOW_NORMAL)命名窗口,再使用resizeWindow(窗口名称, 窗口宽度, 窗口高度) 缩放窗口,最后使用 imshow() 显示图像,注意三者都 * 在同一个窗口名称上 * 操作。

销毁窗口

  • destroyWindow(窗口名称): 单独关闭某个显示窗口
  • destroyAllWindows(): 关闭所有显示窗口。

写入图片: imwrite

定义

1
imwrite(<写入的文件名称>, < 图像实例 >)

注:文件名称的后缀决定了图片文件的格式

视频相关

常用函数

方法 含义
cap = cv2.VideoCapture(‘文件名称’) 构建视频文件的 cap 实例
cap.read() 逐帧提取视频,每一帧为一幅图像 < br /> 方法返回的是一个二元组:
下标 0 的元素值为 True 或 False 如果为 Flase 表示读取文件完成。
下标 1 的元素为图像对象,也是一个 numpy 数组类型的数据。
cap.isOpened() 检查 cap 实例是否已经打开
cap.release() 释放实例

从视频文件获取图像

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import cv2

cap = cv2.VideoCapture('..\\vtest.avi')

while cap.isOpened():
ret, img = cap.read()
if ret is not True:
print("读取完成,退出")
break
#处理 img
cv2.imshow('vedio', img)

#检查按键
key = cv2.waitKey(20) & 0xff
if key == ord('q') or key == ord('Q') :
break

print('cap.isOpened():',cap.isOpened())
cap.release()
print('cap.isOpened():',cap.isOpened())

从相机获取图像

打开相机需要用相机的设备编号(数值型整数)作为入参传入 VideoCapture(相机编号),比如 cap = cv2.VideoCapture(0) 构建编号为 0 的相机访问实例,第 2 台相机则传入 1,以此类推,后续步骤的处理方法和读取视频文件一样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import cv2

cap = cv2.VideoCapture(0)

while cap.isOpened():
ret, img = cap.read()
if ret is not True:
print("读取失败,退出")
break
#处理 img
cv2.imshow('vedio', img)

#检查按键
key = cv2.waitKey(20) & 0xff
if key == ord('q') or key == ord('Q') :
break

print('cap.isOpened():',cap.isOpened())
cap.release()
print('cap.isOpened():',cap.isOpened())

写入视频文件

VideoWriter 对象

参数

  1. 文件名称
  2. 编码方式,其中编码方式和文件名称后缀有对应关系
  3. 每秒写入的帧数,参考数值为 25,符合人眼习惯
  4. 图像大小,int 类型

常用的文件名称后缀和编码方式

文件后缀 编码方式
avi XVID
avi MJPG
avi mp4v(小写)
mp4 mp4v(小写)

使用:创建 VideoWriter_fourcc 对象

1
2
3
4
# 方法 1
fourcc=cv2.VideoWriter_fourcc('M','J','P','G')
# 方法 2
fourcc=cv2.VideoWriter_fourcc(*'MJPG')

图像大小获取

cat.get(propId) 方法获取,但是该方法获取的是 float 类型,需要转换为 int 类型再传入 VideoWriter

举例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import cv2

#获取图像宽高
cap = cv2.VideoCapture(0)
width = cap.get(cv2.CAP_PROP_FRAME_WIDTH)
height = cap.get(cv2.CAP_PROP_FRAME_HEIGHT)
# 转为 int 型
width = int(width)
height = int(height)
print(width,height)

#创建 VideoWriter 对象
fourcc = cv2.VideoWriter_fourcc(*'XVID')
out = cv2.VideoWriter('output.avi', fourcc, 25.0, (width, height))
fourcc = cv2.VideoWriter_fourcc(*'MJPG')
out2 = cv2.VideoWriter('output2.avi', fourcc, 25.0, (width, height))
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
out3 = cv2.VideoWriter('output3.mp4', fourcc, 25.0, (width, height))

while cap.isOpened():
ret, img = cap.read()
print(img.shape)
if ret is not True:
print("读取失败,退出")
break

#处理 img
cv2.imshow('vedio', img)
out.write(img)
out2.write(img)
out3.write(img)

#检查按键
key = cv2.waitKey(1) & 0xff
if key == ord('q') or key == ord('Q') :
break

cap.release()
out.release()
out2.release()
out3.release()

输出文字

乱码问题 (还是不要用中文吧)

**1、指定已有字体 **

查看字体

1
fontList = [i for i in dir(cv2) if (i.startswith("FONT"))]

2、使用 pillow

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import cv2
import numpy as np
from PIL import Image,ImageDraw,ImageFont

# 白色背景
img = np.full((512,512,3),255,np.uint8)
img_pil = Image.fromarray(cv2.cvtColor(img,cv2.COLOR_BGR2RGB))

text = 'VX: 桔子 code'
# 起始位置
start = (10,200)
# 微软雅黑字体,和具体操作系统相关
fonttype = 'msyh.ttc'
# 字体大小
fontscale = 30
font = ImageFont.truetype(fonttype,fontscale)
draw = ImageDraw.Draw(img_pil)
#PIL 中 BGR=(255,0,0) 表示红色
draw.text(start,text,font=font,fill=(255,0,0))
#PIL 图片转换为 numpy
img_ocv = np.array(img_pil)
#PIL 格式转换为 OpenCV 的 BGR 格式
img = cv2.cvtColor(img_ocv,cv2.COLOR_RGB2BGR)

cv2.imshow('juzicode.com',img)
cv2.waitKey()

图像属性

shape 属性

行列数

  • 行对应 img.shape[0]
  • 列对应 img.shape[1]

通道数

  • 必须是非灰度图才有通道数 *,对应 img.shape[2]

其他属性

属性 含义
ndim 维度 <=> len(img.shape)
itemsize 单个数据长度
size 总长度:有多少个数据
nbytes 占用的内存空间 = $itemsize \times size$
shape 形状 (type: tuple)
data 数据 buffer

dtype 属性

可以通过 img.astype(<np.uint32>) 转换得到,进行转换后看到 dtype 的变化,以及因为 dtype 变化引起 itemsize 相关属性的变化。

像素操作

通道

三通道: RGB

四通道: RGB + ahpha(透明度)

通道分离

方法 1

使用 cv2.split(img)

方法 2

使用索引方式(切片),如:r = img[:, :, 0]

通道合并

方法 1

使用 cv2.merge((r, g, b))

方法 2

1
2
3
4
5
6
newImg = np.zeros(img.shape, dtype=np.uint8)
for i in range(img.ndim):
newImg[:, :, i] = img[:, :, i]

cv2.imshow('new img', newImg)
cv2.waitKey(0)
  • 注 *:np.zeros 一定要指定 dtype 为 np.uint8

图像的加法

add()

cv2.add(img1, img2): 超过阈值会截断

+

img1 + img2: 超过阈值会溢出,也就是说对 255 取模

addWeighted()

参数

  1. 第 1 个参数是图像 1
  2. 第 2 个参数是图像 1 的权值
  3. 第 3 个参数为图像 2
  4. 第 4 个参数为图像 2 的权值
  5. 第 5 个参数附加数值 gamma,单个数值,即使是多通道图像也使用单个数值;
  6. 第 6 个参数可选,返回图像的实例,等同于函数返回结果
  7. 第 7 个参数可选,dtype,表示像素值的数据类型

图像的减法

** 目的:** 比较 2 幅图像的差异,比如判断前后 2 个时间点的图像是否发生了变化

subtract()

subtract(img1, img2) : img1 - img2,但是如果小于 0,那么就会截断为 0

-

计算结果对 256 求模运算

absdiff()

绝对值减法

图像和标量加减

图像之间的加减运算可以看成是 2 个向量之间的加减,将相同下标之间的元素值进行加减运算,如果和标量进行加减,则是将图像的每一个元素都和这个标量值进行加减。

当标量值只是 1 个数值时,只会对多通道图像的第 1 个通道进行运算,如果要进行多通道运算,标量值则使用一个包含 4 个数值的元组表示,即使是 3 通道的彩色图像也要用 4 元组表示这个标量值。

图像的乘法

multiply()

定义

dst=cv2.multiply(src1, src2[, dst[, scale[, dtype]]])

参数

  • src1 和 src2 为图像对象
  • 可选参数 scale 为放大倍数
  • 结果 dst 等于 $saturate(scale \times src1 \times src2)$

规则

multiply() 遵循 * 饱和运算规则 *,比如 uint8 类型的数据如果超过 255 会被截断到 255。

符号乘法 *

实际上就是 Numpy 数组的乘法, 和用 +/- 做 numpy 加减法一样, 在数值类型表示范围的上限加 1 取模,比如 uint8 类型的数据对 256 取模

图像的除法

divide()

用法

** 用法 1**

1
dst = cv2.divide(src1, src2[, dst[, scale[, dtype]]])
  • src1 和 src2 都是图像对象
  • scale: 制定 src1 的方法倍数
  • $dst = saturate(src1 \times scale \div src2)$

** 用法 2**

1
dst = cv2.divide(scale, src2[, dst[, dtype]])
  • scale: 数值类型
  • src2: 图像对象
  • $dst = saturate(scale \div src2)$

补充

  • uint8 等整数类型的除法,运算后的结果会做四舍五入取整
  • divide() 遵守 * 饱和运算规则 *

/

对应元素直接进行数学运算

divide() 除法中的 0

当有元素为 0 且作为被除数时,divide() 计算仍然是有实际意义的

scale 参数

scale 参数的用法比较特殊,要想实现 $scale \div src2$ 的用法,必须显式地声明形参的名称。

图像位运算

按位取反 bitwise_not()

将数值根据每个 bit 位 1 变 0,0 变 1,比如 0xf0 按位取反就变成了 0x0f

如果是 uint8 类型的数据,取反前后的数据相加结果为 0xff(255)

按位与 bitwise_and()

1
dst = cv2.bitwise_or(src1, src2[, dst[, mask]])

按位或 bitwise_or()

1
dst = cv2.bitwise_or(src1, src2[, dst[, mask]])

按位异或 bitwise_nor()

1
dst = cv2.bitwise_or(src1, src2[, dst[, mask]])

注意

  • 2 个图像的按位操作和算术运算一样,也要求 2 个图像的大小一样,通道数一样。不同于算术运算数据类型不一样时通过 dtype 声明新生成图像的数据类型,按位运算的接口中根本就没有 dtype 参数,所以位运算中 2 个图像的数据类型也必须一致。
  • 处理标量数据时不会自动填充第 4 通道为 0 而直接报错了,所以在处理 4 通道图像时则必须使用四元组。一个好的编程习惯是不管图像是多少通道的都使用四元组表示这个标量,如果不想对某些通道进行位运算,则用相应的全 0 或全 f 代替,比如一个 3 通道的 uint8 类型的图像,只需要对 2 通道和 0x33 相与,构造的四元组就是(0xff,0x33,0xff,0xff)。

色彩空间变换

cvtColor()

原型

1
dst=cv2.cvtColor(src, code[, dst[, dstCn]])

遍历所有色彩空间转换名称

1
colors = [i for in dir(cv) if (i.startswith('COLOR_'))]

applyColorMap()

原型

1
cv2.applyColorMap(src, colormap[, dst])
  • src 为输入图像,可以是单通道或 3 通道的 8bit 图像。

  • colormap 为颜色图模式,可以传入整数 0~21 对应各种不同的颜色图,或者用 cv2.COLORMAP_AUTUMN(等价于 0)、cv2.COLORMAP_BONE(等价于 1)等方式传入,OpenCV 源码头文件中定义的 22 种模式如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    //! GNU Octave/MATLAB equivalent colormaps
    enum ColormapTypes
    {
    COLORMAP_AUTUMN = 0,
    COLORMAP_BONE = 1,
    COLORMAP_JET = 2,
    COLORMAP_WINTER = 3,
    COLORMAP_RAINBOW = 4,
    COLORMAP_OCEAN = 5,
    COLORMAP_SUMMER = 6,
    COLORMAP_SPRING = 7,
    COLORMAP_COOL = 8,
    COLORMAP_HSV = 9,
    COLORMAP_PINK = 10,
    COLORMAP_HOT = 11,
    COLORMAP_PARULA = 12,
    COLORMAP_MAGMA = 13,
    COLORMAP_INFERNO = 14,
    COLORMAP_PLASMA = 15,
    COLORMAP_VIRIDIS = 16,
    COLORMAP_CIVIDIS = 17,
    COLORMAP_TWILIGHT = 18,
    COLORMAP_TWILIGHT_SHIFTED = 19,
    COLORMAP_TURBO = 20,
    COLORMAP_DEEPGREEN = 21
    };

几何空间变换

缩放

原型

1
dst=cv2.resize(src, dsize[, dst[, fx[, fy[, interpolation]]]])

参数

  • src:源图像;
  • dsize:缩放后目标图像的尺寸,如果设置为 0,目标图像则使用源图像的尺寸乘以 fx 和 fy 得到;dsize 优先级高于 fx 和 fy,如果设置了 dsize,后面的 fx 和 fy 设置无效;
  • fx 和 fy:dsize 未设置的情况下,使用 fx 和 fy 分别作为宽度和高度的放大倍数;
  • interpolation:插值方法,默认使用双线性插值 cv2.INTER_LINEAR;

转置

作用

实现像素下标的 x 和 y 轴坐标进行对调:dst(i,j)=src(j,i)

原型

1
dst = cv2.transpose(src[, dst])

注意

不可以使用 numpy 的 transpose() 转置和 T 属性,会出现数组下标访问越界

翻转

作用

实现水平翻转、垂直翻转和双向翻转

原型

1
dst = cv2.flip(src, flipCode[, dst])

参数含义

  • src: 源图像
  • flipCode: 翻转方式,其中:0,水平轴翻转;大于 0 为垂直轴翻转;小于 0 作双向翻转

仿射变换

原型

1
dst=cv2.warpAffine(src, M, dsize[, dst[, flags[, borderMode[, borderValue]]]])
  • src: 输入图像。
  • M: 2×3 2 行 3 列变换矩阵。
  • dsize: 输出图像的大小。
  • dst: 可选,输出图像,由 dsize 指定大小,type 和 src 一样。
  • flags: 可选,插值方法
  • borderMode: 可选,边界像素模式
  • borderValue: 可选,边界填充值; 默认为 0。

平移

手动指定算子 M=[[1,0,X],[0,1,Y]] 可以实现图像的移位

  • X 表示向图像 x 方向(向右)移动的像素值
  • Y 表示图像向 y 方向(向下)移动的像素值

旋转

步骤

用 getRotationMatrix2D() 方法构造出 warpAffine() 的算子 M

getRotationMatrix2D() 原型

1
retval=cv2.getRotationMatrix2D(center, angle, scale)
  • center:旋转中心位置
  • angle:旋转角度
  • scale:缩放比例,不缩放时为 1

矫正

概念

因为拍摄角度或成像设备的原因发生畸变,可以用仿射变换进行矫正

步骤

使用 getAffineTransform() 构建 M 算子,入参为变换前和变化后的 2 组坐标点,每组坐标点包含 3 个位置参数

**getAffineTransform() 例子 **

1
2
3
pts1 = np.float32([[10,71],[37,320],[550,240]])
pts2 = np.float32([[10,10],[0,300],[550,300]])
M = cv2.getAffineTransform(pts1,pts2)

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import matplotlib.pyplot as plt
import numpy as np
import cv2

plt.rc('font',family='Youyuan',size='9')
plt.rc('axes',unicode_minus='False')

img = cv2.imread('..\\samples\\data\\imageTextR.png')
rows,cols,_ = img.shape

pts1 = np.float32([[10,71],[37,320],[550,240]])
pts2 = np.float32([[10,10],[0,300],[550,300]])
M = cv2.getAffineTransform(pts1,pts2)
img_ret1 = cv2.warpAffine(img,M,(cols,rows))

fig,ax = plt.subplots(1,2)
ax[0].set_title('VX: 桔子 code 原图')
ax[0].imshow(cv2.cvtColor(img,cv2.COLOR_BGR2RGB)) #matplotlib 显示图像为 rgb 格式
ax[1].set_title('变换后')
ax[1].imshow(cv2.cvtColor(img_ret1,cv2.COLOR_BGR2RGB))
#ax[0,0].axis('off');ax[0,1].axis('off');ax[1,0].axis('off');ax[1,1].axis('off')# 关闭坐标轴显示
plt.show()

旋转

原型

1
cv2.rotate(src, rotateCode[, dst]) -> dst

参数

  • src: 源图像

  • rotateCode: 可选 3 种参数

    rotateCode 含义
    cv2.ROTATE_90_CLOCKWISE 顺时针旋转 90 度
    cv2.ROTATE_180 旋转 180 度,不区分顺时针或逆时针,效果一样
    cv2.ROTATE_90_COUNTERCLOCKWISE 逆时针旋转 90 度,等同于顺时针 270 度

本质

是对 flip()transpose() 的封装实现的

透视变换

概念

和仿射变换一样,透视变换也需要先构建变换 kernel,不过仿射变换只需要 3 个点构建,透视变换则需要找到 4 个点构建 kernel。

原型

1
cv2.warpPerspective(src,M,dsize[,dst[,flags[,borderMode[,borderValue]]]])->dst

参数

  • src: 输入图像。
  • M: 3×3 3 行 3 列变换矩阵。
  • dsize: 输出图像的大小。
  • dst: 可选,输出图像,由 dsize 指定大小,数据类型和 src 一样。
  • flags: 可选,插值方法
  • borderMode: 可选,边界像素模式
  • borderValue: 可选,边界填充值; 默认为 0。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import matplotlib.pyplot as plt
import numpy as np
import cv2

plt.rc('font',family='Youyuan',size='9')
plt.rc('axes',unicode_minus='False')

img_src = cv2.imread('..\\samples\\data\\left02.jpg')
#构建 kernel
pts1 = np.float32([[192,40],[610,122],[216,363],[465,415]])
pts2 = np.float32([[0,0],[300,0],[0,350],[300,350]])
kernel = cv2.getPerspectiveTransform(pts1,pts2)
print('kernel:',kernel)
#透视变换,这里图像大小参考 pts2
img_pers = cv2.warpPerspective(img_src,kernel,(300,350))
#显示
fig,ax = plt.subplots(1,2)
ax[0].set_title('img_src')
ax[0].imshow(cv2.cvtColor(img_src,cv2.COLOR_BGR2RGB)) #matplotlib 显示图像为 rgb 格式
ax[1].set_title('img_pers')
ax[1].imshow(cv2.cvtColor(img_pers,cv2.COLOR_BGR2RGB))
plt.show()

阈值化

概念

概念

有些场合也称二值化,是图像分割的一种

作用

一般用于将感兴趣区域从背景中区分出来

方法

将每个像素和阈值进行对比,分离出来需要的像素设置为特定白色的 255 或者黑色 0,具体看实际的使用需求而定

threshold()

原型

1
cv2.threshold(src, thresh, maxval, type[, dst]) -> retval, dst

返回值

该方法返回 2 个值

  • 第 1 个值 retval 为阈值
  • 第 2 个值 dst 为阈值化后的图像

参数

  • src:源图像,8bit 或者 32bit 浮点类型,当 type 没有使用 cv2.THRESH_OTSUcv2.THRESH_TRIANGLE 标志时可以是多通道图像

  • thresh:比较的阈值;

  • maxval:阈值化方法为 THRESH_BINARYTHRESH_BINARY_INV 时单个像素转换后的最大值;

  • type:阈值化类型;

    标志 标志值 dst(x,y) 取值 条件 备注
    cv2.THRESH_BINARY 0 maxval if src(x,y)>thresh; 取值只有 2 种,真正意义的 “二值化”
    0 otherwise
    cv2.THRESH_BINARY_INV 1 0 if src(x,y)>thresh;
    maxval otherwise
    cv2.THRESH_TRUNC 2 threshold if src(x,y)>thresh; 最后的取值有多种
    src(x,y) otherwise
    cv2.THRESH_TOZERO 3 src(x,y) if src(x,y)>thresh;
    0 otherwise
    cv2.THRESH_TOZERO_INV 4 0 if src(x,y)>thresh;
    src(x,y) otherwise
    cv2.THRESH_MASK 7 / /
    cv2.THRESH_OTSU 8 / 标志位,使用大津法选择最佳阈值
    cv2.THRESH_TRIANGLE 16 / 标志位,使用三角算法选择最佳阈值

    注意

    cv2.THRESH_OTSU 或 cv2.THRESH_TRIANGLE 的使用比较特殊,并不能单独使用,需要和其他类型的数值按位或一起传入。

    比如这样使用 type=cv2.THRESH_BINARY | cv2.THRESH_OTSU。当然在实际使用中也常见将 cv2.THRESH_OTSUcv2.THRESH_TRIANGLE 这 2 种取值之一和前 5 种 type 相加,比如 type=cv2.THRESH_BINARY + cv2.THRESH_OTSU,这是因 cv2.THRESH_OTSUcv2.THRESH_TRIANGLE 和其他 5 种 type 按位或和相加得到的值是一致的。从上面的对照表也可以看到,如果使用 cv2.THRESH_BINARYcv2.THRESH_BINARY_INV 得到的图像像素值只有 2 种,要么是 maxval,要么是 0,可以称得上真正意义上的 “二值化”。

自适应阈值

原型

1
cv2.adaptiveThreshold(src, maxValue, adaptiveMethod, thresholdType, blockSize, C[, dst]) -> dst

参数

  • src:源图像;
  • maxValue:单个像素转换后的最大值;
  • adaptiveMethod:自适应方法:cv2.ADAPTIVE_THRESH_MEAN_C(平均法)或 cv2.ADAPTIVE_THRESH_GAUSSIAN_C(高斯法)
    • cv2.ADAPTIVE_THRESH_MEAN_C(平均法):这时阈值等于窗口大小为 blockSize 的临近像素点的平均值减去 C;
    • cv2.ADAPTIVE_THRESH_GAUSSIAN_C(高斯法):阈值等于窗口大小为 blockSize 的临近像素点高斯窗口互相关加权和减去 C。
  • thresholdType:阈值化类型,THRESH_BINARY 或 THRESH_BINARY_INV 二选一;
  • blockSize:计算某个像素使用的阈值时采用的窗口大小,奇数值;
  • C:平均值或加权平均值减去的常量值;可以为正值,0 或负值;

总结

  • threshold() 方法使用大津法和三角法时不需要指定阈值,会自动根据像素值求出阈值,其他方法则需要指定阈值的大小。
  • threshold() 方法使用全局单一阈值,而 adaptiveThreshold() 方法使用局部阈值,所以在处理光线不均匀图片时能取得更好的

平滑处理

目的

图像在生成、传输或存储过程中可能因为外界干扰产生噪声,从而使图像在视觉上表现为出现一些孤立点或者像素值突然变化的点,图像平滑处理的目的就是 ==为了消除图像中的这类噪声==。

滑动窗口

定义

也叫滤波器模板、kernel。用这个 ksize=3×3 的窗口作用于原始图像上的每一个像素,被这个窗口覆盖的 9 个像素点都参与计算,这样在该像素点上就会得到一个新的像素值,当窗口沿着图像逐个像素进行计算,就会得到一幅新的图像。

滤波器模板的不同就构成了滤波算法的差异:

  • 均值平滑算法中滑动窗口中各个像素点的系数均为 1/(窗口高 * 窗口宽)
  • 高斯平滑中系数和中心点的距离满足高斯分布。

边沿处理

填 0、填 1、复制边沿等

均值平滑

原型

1
dst=cv2.blur(src, ksize[, dst[, anchor[, borderType]]])

参数

  • src:源图像,通道数不限,数据类型必须为 CV_8U, CV_16U, CV_16S, CV_32F or CV_64F;
  • ksize:kernel 尺寸、窗口大小,二元组类型,元素值可以是偶数或奇数;
  • anchor:锚点,默认为(-1,-1),作用于滑动窗口的中心点;
  • borderType:边界处理类型;

效果

ksize 越大,图像越模糊,清晰度越低。

中值平滑

概念

中值平滑和均值平滑一样也用到了滑动窗口,但是它并不是计算滑动窗口中的某种加权和,而是使用原图像滑动窗口中所有像素值排序后的中值作为新图像的像素值。

原型

1
dst=cv2.medianBlur(src, ksize[, dst])

参数

  • src:源图像,通道数可以是 1,3 或 4,当 ksize 为 3 或者 5 时,数据类型可以是 CV_8U, CV_16U, CV_32F,当使用更大的 ksize 时,数据类型只能是 CV_8U;
  • ksize:kernel 尺寸、窗口大小,整数型,大于 1 的奇数值;

像素值对比

中值后模糊原因

均值和中值都会降低图像变化的程度

小结

平滑处理是图像滤波的一种,可以看做是低通滤波,它会 == 消除图像的高频 “信号”==,让图像看起来更模糊、平滑,通过将变化前后的图像像素值绘制曲线可以更形象地观察到这种平滑效果。

高斯平滑

概念

高斯平滑则 ==根据距离中心点的间距远近其权重会不同==,这种方式看起来更符合” 惯例”:身边的人对你影响会更大。

高斯分布:正态分布

滑动窗口的权重: 正态分布的归一化

原型

1
dst=cv2.GaussianBlur(src, ksize, sigmaX[, dst[, sigmaY[, borderType]]])

参数

  • src:通道数任意,实际处理是分通道处理;图像深度只能是 CV_8U, CV_16U, CV_16S, CV_32F or CV_64F;
  • ksize:元组类型,窗口大小,宽度和高度可以不一样,但是必须是正的奇数;如果设置为 0,则根据 sigma 计算得到。
  • sigmaX:图像 X 方向的标准差,对应前述二维高斯分布的σ1;
  • sigmaY:图像 Y 方向的标准差,对应前述二维高斯分布的σ2,如果传入 0,会等于 sigmaX,如果 sigmaX 和 sigmaY 都传入 0,sigmaX 和 sigmaX 则根据 ksize 计算;
  • borderType:边界处理方式;

注意

因为都是以滑动窗口中心点为原点,为了保证中心点 (x,y)=(0,0) 的权重为最大值,所以在 OpenCV 的高斯平滑中 μ1 和μ2 都设置为 0,这样在调用高斯平滑函数时只需要传入σ1(sigmaX)和σ2(sigmaY)。

效果

  • ksize 越大,图像越模糊
  • ksize 保持不变,sigma 越大时,原点的取值越小,周围点的取值更大,对应到图像上中心点的权重越低,周围点权重越高,所以 sigma 越大图像越模糊。

双边平滑

背景

均值、中值、高斯平滑的去躁是一种 “无差别攻击”,所有的像素都受到同一个加权系数的影响,所以在平滑过程中也会影响到图像的边沿(像素值突变的地方 ==???==)

双边滤波则可以在 ==去除噪声的同时又能保持图像的边沿==,也就是传说中的” 去噪保边”。

加权系数

函数原型

1
dst=cv2.bilateralFilter(src, d, sigmaColor, sigmaSpace[, dst[, borderType]])

参数

  • src:8bit 或浮点类型;1 或 3 通道;
  • d:窗口大小,如果为非正数,根据 sigmaSpace 计算;d>5 时速度会比较慢,当噪声比较严重时可以选择 d>=9,但是此时不适合对时间敏感的处理;
  • sigmaColor:亮度差的 sigma 参数;
  • sigmaSpace:空间距离的 sigma 参数,同时作用于图像的 X 和 Y(行、列)2 个方向;
  • borderType:边界处理方式;

总结

高斯平滑对比均值和中值平滑其取值更符合 “惯例”,在空间距离上距离越近的像素用来计算新像素的值其权重越大。均值平滑、中值平滑和高斯平滑会对整幅图像实现无差别的平滑,一个固定系数的滑动窗口作用于整个图像,所以平滑后的图像虽然处理掉了噪声,但是边沿部分也会被削弱。而双边平滑在高斯平滑使用的系数基础上乘以像素差值的高斯函数,和中心点像素差值越大整个系数值越小,最后就能达到去躁保边的效果。

形态学变换

形态学概念

形态学变换是基于图像形状的变换过程,通常用来处理二值图像,当然也可以用在灰度图上。

OpenCV 中的形态学变换同平滑处理一样也是基于一种 “滑动窗口” 的操作,不过在形态学变换中 “滑动窗口” 有一个更专业的名词:“结构元”,也可以像平滑处理那样称呼为 kernel。

结构元的形状有方形、十字形、椭圆形等,其形状决定了形态学变换的特点。形态学变换主要有腐蚀、膨胀、开操作、闭操作等等。

结构元生成

结构元生成函数用来生成形态学变换的 kernel 参数。

函数原型

1
cv2.getStructuringElement(shape, ksize[, anchor]) ->retval

参数

  • shape:结构元 (kernel) 的形状;

    shape 属性 形状
    cv2.MORPH_RECT 方形,所有的数值均为 1
    cv2.MORPH_CROSS 十字交叉形,在锚点坐标的水平和竖直方向的元素为 1,其他为 0
    cv2.MORPH_ELLIPSE 椭圆形
  • ksize:结构元 (kernel) 的大小;

  • anchor:锚点,默认使用 (-1,-1) 表示中心点;

不同 kernel 的影响

以膨胀为例来看

  • 使用方形 MORPH_RECT 的结构元时,新图像的边界看起来仍然是方方正正的
  • 使用十字形 MORPH_CROSS 和椭圆形 MORPH_ELLIPSE 的结构元时,边界要显得 “圆滑” 的多。

腐蚀

概念

腐蚀操作可以将边界的白色(前景)像素 “腐蚀” 掉,但仍能保持大部分白色。类似平滑处理的滑动窗口,用某种结构元在图像上滑动,当结构元覆盖原始图像中的所有像素都为 “1” 时,新图像中该像素点的值才为“1”(CV8U 为 255)。腐蚀可以用来 == 去除噪声、去掉“粘连”==。

函数原型

1
cv2.erode(src, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]]]) ->dst

参数

  • src:通道数任意;图像深度只能是 CV_8U, CV_16U, CV_16S, CV_32F or CV_64F;
  • kernel:可以由 getStructuringElement() 构建;
  • dst:输出图像,通道数和数据类型同 src;
  • anchor:锚点,默认使用 (-1,-1) 表示中心点;
  • iterations:腐蚀次数;
  • borderType:边界类型;
  • borderValue:边界值;

效果

kernel 的 ksize 越大,iterations 次数越多,图像看起来越 “廋”。

膨胀

概念

膨胀是腐蚀的逆操作,可以将边界的白色(前景)像素 “生长” 扩大。滑动窗口经过白色像素时,只要结构元中有 1 个像素为 “1” 时,新图像中该像素点的值就为“1”(CV8U 为 255)。

== 膨胀可以用来增强连接、填充凹痕。==

函数原型

1
cv2.dilate(src, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]]]) ->dst

参数

  • src:通道数任意;图像深度只能是 CV_8U, CV_16U, CV_16S, CV_32F or CV_64F;
  • kernel:可以由 getStructuringElement() 构建;
  • dst:输出图像,通道数和数据类型同 src;
  • anchor:锚点,默认使用 (-1,-1) 表示中心点;
  • iterations:膨胀次数;
  • borderType:边界类型;
  • borderValue:边界值;

效果

kernel 的 ksize 越大,iterations 次数越多,图像看起来越 “胖”。

morphologyEx() 函数

函数原型

1
cv2.morphologyEx(src, op, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]]]) ->dst

参数

  • src:源图像,通道数任意;图像深度只能是 CV_8U, CV_16U, CV_16S, CV_32F or CV_64F;其中 op 为 cv2.MORPH_HITMISS 时仅支持 CV_8UC1;

  • op:变换方式;

  • kernel:可以由 getStructuringElement() 构建

    ​ op 为 cv2.MORPH_HITMISS 时则由子图构建;

  • dst:输出图像,通道数和数据类型同 src;

  • anchor:锚点,默认使用 (-1,-1) 表示中心点;

  • iterations:迭代次数;

  • borderType:边界类型;

  • borderValue:边界值;

op 值与 erode、dilate 的关系

形态学变换 op 标志位 与 erode 和 dilate 关系
腐蚀 cv2.MORPH_ERODE dst=erode(src,element)
膨胀 cv2.MORPH_DILATE dst=dilate(src,element)
开操作 cv2.MORPH_OPEN dst=dilate(erode(src,element))
闭操作 cv2.MORPH_CLOSE dst=erode(dilate(src,element))
梯度 cv2.MORPH_GRADIENT dst=dilate(src,element)−erode(src,element)
顶帽 cv2.MORPH_TOPHAT dst=src−open(src,element)
黑帽 cv2.MORPH_BLACKHAT dst=close(src,element)−src
击中击不中 cv2.MORPH_HITMISS dst=erode(src,element) & erode(src,element)

开操作

概念

开操作的实质是 ==先进行腐蚀再膨胀==,可以用来消除小于结构元大小的细小区域

示例

1
img_open = cv2.morphologyEx(img_bin,cv2.MORPH_OPEN,kernel,iterations=1)

闭操作

概念

闭操作实际上是 ==先进行膨胀再腐蚀==,因为膨胀可以用来填充孔洞、修复缺失的连接,但是同时也会导致白色轮廓增大,当用同样的结构元 (kernel) 再进行一次腐蚀操作后,就可以保持外形轮廓和原来的一致。

示例

1
img_close = cv2.morphologyEx(img_bin,cv2.MORPH_CLOSE,kernel,iterations=1)

形态学梯度

概念

形态学梯度操作是用膨胀图像减去腐蚀图像的结果,因为膨胀可以增大边沿,腐蚀会缩小边沿,所以形态学梯度变换就 ==能将轮廓提取出来==

示例

1
img_gradient = cv2.morphologyEx(img_bin,cv2.MORPH_GRADIENT,kernel,iterations=1)

顶帽

概念

顶帽变换是用原图减去开操作图像,因为开操作会去除小于结构元的小区域,原图减去开操作图像后,会 ==将开操作去除的小区域保留下来==

示例

1
img_tophat = cv2.morphologyEx(img_bin,cv2.MORPH_TOPHAT,kernel,iterations=1)

黑帽

概念

黑帽变换和顶帽变换则相反,是将闭操作后的图像减去原图,因为闭操作会填充孔洞(小的黑色区域),孔洞部分变成白色,而原图中仍然为黑色,这样就会 ==将原图中的孔洞保留下来并变为白色区域==。

示例

1
img_blackhat = cv2.morphologyEx(img_bin,cv2.MORPH_BLACKHAT,kernel,iterations=1)

击中不击中

概念

击中击不中变换可以 ==用来在原图中查找子图==,假设要查找的图像中包含了多种子图,可以利用某个子图构造出 kernel,经过击中击不中变换就能在该子图中心保留一个非零的点。

** 注意:** 这里构造 kernel 不再是使用 getStructuringElement(),而是需要用子图构造。

一个构造 kernel 的例子如下,首先从子图中读取图像,然后和要做变换的原图做一样的阈值化,接下来构造一个和子图大小一样类型为 np.int8 型的 kernel,其中子图阈值化后值为 255 的位置设置为 1,阈值化后值为 0 的位置设置为 - 1:

1
2
3
4
5
6
#构建 kernel
img_kernel = cv2.imread('..\\samples\\picture\\hitmiss-kernel.bmp',cv2.IMREAD_GRAYSCALE)
_,img_kernel_bin = cv2.threshold(img_kernel,193,255,1) #阈值化,阈值和要做变换的原图一致
kernel = img_kernel.astype(np.int8) #构造一个和子图大小但是类型为 int8 型,可以保存负数
kernel[img_kernel_bin == 255] = 1
kernel[img_kernel_bin == 0] = -1

步骤

  1. 读取原图并进行阈值化
  2. 按照前面的方法构建 kernel
  3. 用 morphologyEx() 进行击中击不中变换
  4. 用 findNonZero() 查找非零点并用 circle() 绘图显示出来

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
## 构建 kernel
img_kernel = cv2.imread('..\\samples\\picture\\hitmiss-kernel.bmp',cv2.IMREAD_GRAYSCALE)
# 阈值化,阈值和要做变换的原图一致
_,img_kernel_bin = cv2.threshold(img_kernel,193,255,1)
# 构造一个和子图大小但是类型为 int8 型,可以保存负数
kernel = img_kernel.astype(np.int8)
kernel[img_kernel_bin == 255] = 1
kernel[img_kernel_bin == 0] = -1

## 击中击不中变换
img_hitmiss = cv2.morphologyEx(img_src_bin,cv2.MORPH_HITMISS ,kernel,iterations=1)
print('countNonZero(img_hitmiss):',cv2.countNonZero(img_hitmiss))

# 绘制中心点
locations = cv2.findNonZero(img_hitmiss)
img_hitmiss_color = cv2.cvtColor(img_hitmiss,cv2.COLOR_GRAY2BGR)
if locations is not None:
print(type(locations), locations.shape ,locations)
print('locations:',locations[0][0]) # 第 2 个 [0] 固定,第 1 个 [0] 表示找到位置的个数
center=locations[0][0][0],locations[0][0][1]
cv2.circle(img_hitmiss_color,center, 15, (0,255,255), 5)
else:
print('未击中')

图像金字塔

概念

图像金字塔是一些列图像的几何,如下图所示。更高层图像尺寸更小,更底层图像尺寸更大,看起来就像是一个金字塔一样

pyrDown

概念

这里的 down 是指图像变小,所以原始图像在金字塔的底部。

步骤

  1. 将当前图像和高斯核卷积:

    【Note】:这个高斯核的尺寸为 5×5 大小,所有元素的值加起来正好为 256,最后再除以 256,得到的加权和正好为 1。其距离最中心越近数值越大,这正好和高斯平滑选择的高斯核类似。这个过程也类似于高斯平滑。

函数原型

1
cv2.pyrDown(src[, dst[, dstsize[, borderType]]]) ->dst

参数

  • src:源图像;
  • dst:目标图像;
  • dstsize:缩放后目标图像的尺寸,必须满足 std::abs(dsize.width2 – ssize.width) <= 2 && std::abs(dsize.height2 – ssize.height) <= 2
  • borderType:边界填充类型;

行为

经过 pyrDown() 处理的图像变得更加模糊 (平滑)。然后移除偶数行和偶数列,然后就能得到和原图相比是原图 1/4 大小的新的图像,在图像金字塔中就位于当前层的上一层。

pyrUp

概念

将图像的尺寸变大,所以原始图像位于图像金字塔的顶层。

函数原型

1
cv2.pyrUp(src[, dst[, dstsize[, borderType]]]) ->dst

参数

  • src:源图像;
  • dst:目标图像;
  • dstsize:缩放后目标图像的尺寸,必须满足 std::abs(dsize.width – ssize.width2) ==dsize.width % 2 && std::abs(dsize.height – ssize.height2)== dsize.height % 2
  • borderType:边界填充类型;

图像梯度

概念

高斯平滑、双边平滑均值平滑、中值平滑 介绍的平滑处理可以看做是图像的 “低通滤波”,它会滤除掉图像的“高频” 部分,使图像看起来更平滑。

图像梯度则可以看做是对图像进行 “高通滤波”,他会滤除图像中的低频部分,==为的是凸显图像的突变部分==

sobel

函数原型

1
dst = cv2.Sobel(src, ddepth, dx, dy[, dst[, ksize[, scale[, delta[, borderType]]]]])

参数

  • src:源图像;
  • ddepth:目标图像深度;
  • dx:x 方向求导阶数;
  • dy:y 方向求导阶数;
  • dst:目标图像;
  • ksize:kernel 尺寸,说明文档上指出必须是 1,3,5,7 中的一个,但是实验可以得到应该是小于 31 的正奇数;如果是 - 1 表示 scharr 滤波;
  • scale:缩放比例,默认为 1;
  • delta:叠加值,默认为 0;
  • borderType:边界填充类型;

效果

dx 与 dy 取值不同的效果

(dx, dy) 效果
(1, 0) 凸显 x 方向的梯度变化
(0, 1) 凸显 y 方向的梯度变化
(0.5, 0.5) 凸显边沿
(1, 1) dx 和 dy 都为 1 表明是与的关系,只有 x 和 y 方向都有梯度的时候才能被检测出来。

ksize 的取值不同

  • ksize 的值越大,梯度信息呈现的越多
  • ksize 相同,dx 的值越小,梯度的细节越多

scharr

背景

当 kernel 的尺寸为 3×3 时,Sobel 计算的结果不是很精确,为了得到更精确的计算结果常采用下图所示的 Scharr kernel

概念

Scharr 变换可以看做是使用了 Scharr 核的 Sobel 变换,是一种经过改进的 Sobel 变换,同样也要区分 x 和 y 方向分开计算梯度。

函数原型

1
dst = cv2.Scharr(src, ddepth, dx, dy[, dst[, scale[, delta[, borderType]]]])

参数

  • src:源图像;
  • ddepth:目标图像深度;
  • dx:x 方向求导阶数;
  • dy:y 方向求导阶数;
  • dst:目标图像;
  • scale:缩放比例,默认为 1;
  • delta:叠加值,默认为 0;
  • borderType:边界填充类型;

Notes

  • scharr() 没有 ksize 参数, scharrkernel 大小固定为 $3 \times 3$

  • dx 和 dy 要满足如下的关系,否则会抛异常

    CV_Assert(dx>= 0 && dy >= 0 && dx + dy == 1 )

    也就是每次只能求 x 方向或者 y 方向单个方向的梯度,而且只能求一阶梯度,不像 Sobel() 中 dx 或 dy 可以设置为更大的值计算更高阶的梯度。

Laplacian

概念

Laplacian 变换是对图像求二阶导数,下图是 2 种 $3 \times 3$ 尺寸的 kernel,这里 ksize 是 Laplacian() 的入参名称

Notes

  • Laplacian() 变换不需要区分图像的 x 和 y 方向计算梯度,从上图的 2 种 kernel 也可以看到其 x 和 y 方向是对称的。

  • 在 Laplacian() 变换中,==ksize 必须是小于 31 的正奇数 ==

    当 ksize 等于 1 时,这时 kernel 的尺寸大小并非是 1,其实际尺寸仍然为 3×3 (看源码)

函数原型

1
dst = cv2.Laplacian(src, ddepth[, dst[, ksize[, scale[, delta[, borderType]]]]])

参数

  • src:源图像;
  • ddepth:目标图像深度;
  • dst:目标图像;
  • ksize:kernel 尺寸,小于 31 的正奇数;如果为 1 仍然是一个 3×3 的 kernel;
  • scale:缩放比例,默认为 1;
  • delta:叠加值,默认为 0;
  • borderType:边界填充类型;

Notes

  • Laplacian 变换中没有 dx 或 dy 参数,因为 Laplacian 是对图像求二阶导数。

效果

  • Laplacian() 中的 ksize 越大,梯度信息越丰富
  • 在相同的 ksize 时,二阶 Sobel 变换和 Laplacian 变换对比看,Laplacian 变换取得的梯度信息要更明显一些。

边沿检测

概念

图像梯度反应的是图像像素值的变化过程,不管变化大小都考虑在内,所以 Sobel, Laplacian 变换得到的是一个多级灰度图。

边沿洁厕也昆虫看做是图像梯度的一种延伸,不过边沿检测更注意图像的 “边沿部分”,图像梯度变化较小的部分会被忽略,只有较大变化的部分保留下来

Canny

概念

canny 边沿检测有低错误率、很好地定位边缘点、单一的边缘点响应等优点

步骤

  1. 高斯滤波器平滑输入图像;
  2. 计算梯度幅值图像和角度方向;
  3. 对梯度幅值图像应用非最大值抑制;
  4. 用双阈值处理和连接分析检测和连接边沿。

非极大值抑制

抑制非极大值的目标(去冗余),从而搜索出局部极大值的目标(找最优)

函数原型

函数原型1

1
edges=cv2.Canny(image, threshold1, threshold1[, edges[, apertureSize[, L2gradient]]])

参数

  • image:8bit源图像,可以是单通道或多通道;
  • threshold1:迟滞阈值1;
  • threshold2:迟滞阈值2,和threshold1没有大小要求,函数内部会调整交换;
  • edges:目标图像,二值图像;
  • apertureSize:kernel尺寸,默认为3;
  • L2gradient:是否使用L2范式,如果设置为True,计算梯度时使用的是2个方向梯度的平方和开平方,如果设置为False,则使用2个方向梯度的绝对值的和;

函数原型2

1
edges=cv2.Canny(dx, dy, threshold1, threshold2[, edges[, L2gradient]])

参数

  • dx:源图像的16bit(CV_16SC1 or CV_16SC3) x方向梯度图像;
  • dy:源图像的16bit(CV_16SC1 or CV_16SC3) y方向梯度图像;
  • threshold1:迟滞阈值1;
  • threshold2:迟滞阈值2;
  • edges:目标图像;
  • L2gradient:是否使用L2范式,如果设置为True,计算梯度时使用的是2个方向梯度的平方和开平方,如果设置为False,则使用2个方向梯度的绝对值的和;

注意

  • 第2种接口形式和第1种实现边沿检测的效果是一样的
  • 第2种形式需要先计算图像的x和y方向的梯度,所以计算梯度的kernel尺寸在第2种接口中就不需要了

ksize差异

  • 相同的threshold值,ksize越大边沿细节越多

    这点和Sobel(),Scharr(),Laplacian()计算图像梯度效果是一样的。

迟滞阈值

即: $\frac{threshold1}{2}$

2个迟滞阈值在函数内部会进行比较,较小者存入low_thresh

阈值宽度

即: $\left | threshold1 - threshold2 \right | $

效果

相同的ksize时,threshold1和threshold2的差值越小,边沿细节越多

像素值

  • canny() 变换后的边沿图像用直方图显示,可以看到变换后的图像是一个二值图像, 像素的取值为0或者255
  • Sobel()、Laplacian() 等梯度变换得到的是一个灰度图

小结

  • Canny() 变换中相同的 threshold 值,ksize 越大边沿细节越多;
  • threshold1 和 threshold2 的差值越小,边沿细节越多;
  • Canny() 变换后得到的是一个二值图像;
  • Canny() 第 2 种接口形式得到的图像效果和第 1 种相比几乎没有差别,因为需要先计算图像 x 和 y 方向的梯度图像,使用起来更繁杂些。

统计函数

非 0 值数量

函数原型

1
cv2.countNonZero(src) -> retval

参数

  • src:输入图像,必须为单通道图像;
  • retval:非零像素值个数

功能

countNonZero()用来统计元素值为非0值的像素点个数。

最小最大值及其位置

函数原型

1
cv2.minMaxLoc(src[, mask])->minVal, maxVal, minLoc, maxLoc

功能

minMaxLoc() 函数返回图像中的元素值的最小值和最大值,以及最小值和最大值的坐标。

参数

  • src:输入图像,必须为单通道图像;
  • mask:掩码;
  • minVal, maxVal, minLoc, maxLoc:依次为最小值,最大值,最小值的坐标,最大值的坐标;

返回值

返回 minLoc 和 maxLoc 的坐标位置是以 OpenCV 中 (x,y) 的形式组织的,但是在 numpy 中下标访问是按照 array[行][列] 形式,类似于 array[y][x] 的形式,所以 minLoc 和 maxLoc 的坐标值不能直接用于 numpy 的下标访问,需要对调后才可以使用

注意

  • minMaxLoc()内部是按照行扫描方式,如果找到一个最小值,后面没有比这个数值更小的数值,那最小值的位置就是最开始出现的那个位置,即使后面出现了这个最小数值相等的数值,找最大值也一样

元素值之和

函数原型

1
cv2.sumElems(src) -> retval

功能

sumElems() 统计所有元素值之和,如果有多通道,分通道计算,返回的是一个四元组,依次对应图像可能包含的第 0,1,2,3 通道,如果单通道图像则只有下标 0 对应的元素有意义,如果是 3 通道则只有前 3 个元素有意义。

参数

  • src:输入图像,可以是单通道,3通道或4通道图像;
  • retval:返回的是一个4元组,分别对应各通道元素的和。

平均值

函数原型

1
cv2.mean(src[, mask]) ->retval

功能

mean() 用来统计单个通道内像素值的平均值,如果有多个通道,分通道计算。

参数

  • src:输入图像,可以是单通道,3 通道或 4 通道图像;
  • mask:可选的掩码;

平均值与标准差

函数原型

1
cv2.meanStdDev(src[, mean[, stddev[, mask]]]) ->mean, stddev

功能

meanStdDev() 用来统计单通道内像素值的平均值和标准差,一次调用返回 2 个结果。

参数

  • src:输入图像,必须为单通道图像;
  • mask:可选的掩码;
  • mean:平均值;
  • stddev:标准差;
  • meanStdDev() 返回的是一个元组,下标 0 为平均值 mean,下标 1 为标准差 stddev。

单行/列的极值、和、均值

函数原型

1
cv2.reduce(src, dim, rtype[, dst[, dtype]]) ->dst

功能

reduce 用来统计二维数组的每一行或每一列中的最小值、最大值、平均值、和。这里 reduce 的含义也可以理解为将二维矩阵压缩成一维向量,压缩后的值根据入参类型可以是最小值、最大值、平均值或者和。

参数

  • src:源图像,可以是单通道也可以是多通道,多通道时分通道计算;
  • dim:如果为 0 表示统计每列的数据等价于压缩成行(row),如果为 1 表示统计每行的数据等价于压缩成列(column);
  • rtype:reduce 操作的类型;
  • dst:目标图像;
  • dtype:目标图像的类型,如果不指定默认为 - 1 表示用源图像 src 的数据类型;
  • dim 参数的理解:如果为 0 表示生成新的数据将是一个行向量,所以是在每一列上操作,将单个的列压缩成一个数值从而组成一个行向量;如果为 1 则表示生成新的数据是一个列向量,在每一行上操作,将单个的行压缩成一个数值从而组成一个列向量。
0%