0%

一、前言

Typora修改字体可在css文件中改动字体,我的环境如下:

1
2
3
系统:Windows 10 家庭中文版
客户端:Typora for Windows 0.11.18
主题:Drake

二、修改字体样式

将如下文件中的--monospace改为想要的字体,按顺序进行优先启用字体。我这里采用Fira Code Retina为英文字体,Noto Serif SC作为中文字体。

注意:需要安装对应字体,字体文件解压到drake主题文件夹内,或者win平台下载字体后双击进行安装。

1
2
3
4
/* theme/drake.css */
::root {
--monospace: "Fira Code Retina", "Noto Serif SC"; /*code font*/
}

三、修改代码块字体大小

我的字体在Typora中设置为18px,因为是笔记本2k屏,16px会有点小。下面是代码块的设置,改动了代码块字体为正文字体的95%,并且调整代码块行高为1.6rem,总体看着比较舒服。

1
2
3
4
5
6
7
8
9
10
11
12
/* theme/drake.css */
/*code block*/
.md-fences {
font-size: 0.95rem; /* 修改了代码块字体大小,和正文字体大小有所区分 */
padding: 0.5rem !important;
border-radius: 2px;
word-wrap: normal;
background-color: var(--code-block-bg-color);
color: var(--code-block-color);
border: none;
line-height: 1.6rem; /* 修改了代码块行高 */
}

四、Fira Code Close ligatures 关闭连字

连字这个特性有的程序员喜欢,有的不喜欢。这里给出两种关闭的方式:

1. 代码块启用连字,正文关闭连字

首先在theme/drake.css文件中将-webkit-font-feature-settings: "liga" on, "calt" on;此句话注释掉。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* theme/drake.css */
body {
font-family: var(--text-font) !important;
color: var(--text-color);
/* 注释此处
-webkit-font-feature-settings: "liga" on, "calt" on;
*/
-webkit-font-smoothing: subpixel-antialiased;
text-rendering: optimizeLegibility;
letter-spacing: 0;
margin: 0;
overflow-x: hidden;
line-height: 1.8rem;
}

然后在theme文件夹中新建base.user.css文件,添加如下代码,即可实现代码块启动连字,正文关闭连字

1
2
3
p {
font-variant-ligatures: none !important;
}

2. 正文和代码块完全关闭连字

theme/drake.css文件中,将-webkit-font-feature-settings:ligacalt属性都改为off,即可实现完全关闭连字。

1
2
3
4
5
6
7
8
9
10
11
12
/* theme/drake.css */
body {
font-family: var(--text-font) !important;
color: var(--text-color);
-webkit-font-feature-settings: "liga" off, "calt" off;
-webkit-font-smoothing: subpixel-antialiased;
text-rendering: optimizeLegibility;
letter-spacing: 0;
margin: 0;
overflow-x: hidden;
line-height: 1.8rem;
}

前言

在网上逛到了用Hugo做的博客,发现Meme主题很好看,研究了一会,发现主要是字体好看,也不打算迁移了,于是便开始设置Hexo的NexT主题为相应的字体。

本人配置:

  • Win10
  • hexo: 5.2.0
  • hexo-cli: 4.2.0
  • node: 12.19.0
  • NexT主题Pisces样式

更换字体

Hugo Meme主题字体:思源宋体 Noto Serif Simplified Chinese

Google Font:https://fonts.google.com/noto/specimen/Noto+Serif+SC

在Hexo Next中更改字体样式,只需要更改NexT的配置文件themes\next_config.yml

将font的enable改为true,并将其中所有的模块的字体都写上Noto Serif SC,这样hexo会自动从fonts host获取字体

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
51
52
# ---------------------------------------------------------------
# Font Settings
# See: https://theme-next.org/docs/theme-settings/#Fonts-Customization
# ---------------------------------------------------------------
# Find fonts on Google Fonts (https://www.google.com/fonts)
# All fonts set here will have the following styles:
# light | light italic | normal | normal italic | bold | bold italic
# Be aware that setting too much fonts will cause site running slowly
# ---------------------------------------------------------------
# To avoid space between header and sidebar in scheme Pisces / Gemini, Web Safe fonts are recommended for `global` (and `title`):
# Arial | Tahoma | Helvetica | Times New Roman | Courier New | Verdana | Georgia | Palatino | Garamond | Comic Sans MS | Trebuchet MS
# ---------------------------------------------------------------

font:
enable: true

# Uri of fonts host, e.g. https://fonts.googleapis.com (Default).
host:

# Font options:
# `external: true` will load this font family from `host` above.
# `family: Times New Roman`. Without any quotes.
# `size: x.x`. Use `em` as unit. Default: 1 (16px)

# Global font settings used for all elements inside <body>.
global:
external: true
family: Noto Serif SC
size:

# Font settings for site title (.site-title).
title:
external: true
family: Noto Serif SC
size:

# Font settings for headlines (<h1> to <h6>).
headings:
external: true
family: Noto Serif SC
size:

# Font settings for posts (.post-body).
posts:
external: true
family: Noto Serif SC
size:

# Font settings for <code> and code blocks.
codes:
external: true
family: Source Code Pro

更改字体大小

问题:hexo next字体大小更改后无效

换完了字体,设置的字体大小明明默认是1em,也就是16px,但正文字体很大,有1.125em,也就是18px。

谷歌了一下,查到除了上方themes\next_config.yml可以配置字体样式和大小,还有一个直接控制字体的文件themes\next\source\css_variables\base.styl

如下就是默认配置,可以看到没什么问题,正文就应该是1em。

1
2
3
4
5
6
7
8
9
// Font size
$font-size-base = (hexo-config('font.enable') and hexo-config('font.global.size') is a 'unit') ? unit(hexo-config('font.global.size'), em) : 1em;
$font-size-smallest = .75em;
$font-size-smaller = .8125em;
$font-size-small = .875em;
$font-size-medium = 1em;
$font-size-large = 1.125em;
$font-size-larger = 1.25em;
$font-size-largest = 1.5em;

问题定位:既然网页上正文字体的样式是1.125em,直接在themes中查1.125em,发现只有上方代码里的font-size-large是1.125。于是搜索font-size-large,终于被我逮到了,说的就是你themes\next\source\css_common\components\post\post.styl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
.post-body {
font-family: $font-family-posts;
word-wrap();

+desktop-large() {
font-size: $font-size-large ;
}

.exturl .fa {
font-size: $font-size-small;
margin-left: 4px;
}

.image-caption, .figure .caption {
color: $grey-dark;
font-size: $font-size-small;
font-weight: bold;
line-height: 1;
margin: -20px auto 15px;
text-align: center;
}
}

注意5-7行代码,这个文件默认配置的正文字体大小是font-size-large,怪不得正文不是1em,改成font-size-medium就好了。

更改代码块字体

将themes\next\source\css_variables\base.styl文件中table-font-size改为80%,这样代码块字体就自动调整为正文的80%

1
2
3
4
5
6
7
8
9
// Table
// --------------------------------------------------
$table-border-color = $grey-lighter;
$table-font-size = $font-size-small;
$table-cell-border-bottom-color = $grey-lighter;
$table-row-odd-bg-color = #f9f9f9;
$table-row-odd-bg-color-dark = #282828;
$table-row-hover-bg-color = $whitesmoke;
$table-row-hover-bg-color-dark = #363636;

参考:

  1. 官方文档-设置字体
  2. Hexo Next 主题字体相关配置

Java输入输出方法总结

Scanner库

在做牛客OJ输入输出练习场时的笔记,JDK版本为1.8.0_251,idea为2020.1.2,所写很多是个人理解,敬请指正。下文的代码均在IDEA上运行调试,输入的每行都会有”\n”。并且下面函数,只要scanner对象没有读到值,为空,那么当前函数都会等待输入,不会执行下一句代码。

基本用法

创建Scanner的一个实例,并将System.in传入Scanner类构造器中,这时就算在控制台输入信息,scan对象也不会读取任何东西(通过调试猜想)。

20210316160146

可以看到上图中变量框内buf为””,代表当前scan读入的值为空。

1
2
3
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
}

Scanner读入数据可以使用next()和nextLine()函数。

next()函数用法

next()函数会等待当前的输入,并且读入当前的有效字符

  1. 未读到有效字符时,会一直等待读入有效字符,这时,空格和回车是无效字符;
  2. 当读到有效字符时,空格会成为无效字符,空格后的字符不会读入,而回车作为终止符,代表这一行输入结束,next()函数也结束,例如用例2和用例3;
  3. 未读到终止符时,一直等待输入。
1
2
3
4
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
System.out.print("输入的数据为:" + scan.next());
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
注:"[in]:"后的字符为输入,"[out]:"后的字符为输出。
用例1
[in]:hello world
[out]:输入的数据为:hello
---
用例2
[in]: hello world
[out]:输入的数据为:hello
---
用例3
[in]:

hello world
[out]:输入的数据为:hello

nextLine()函数用法

nextLine()函数和next函数基本功能一致,但是nextLine()会将空格作为有效字符读入,回车作为终止符。

1
2
3
4
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
System.out.println("输入的数据为:" + scan.nextLine());
}
1
2
3
4
5
6
7
8
9
10
11
12
用例1
[in]: hello world
[out]: hello world

用例2(这里输入和输出中three space前后都有三个空格,空格作为有效字符)
[in]: three space
[out]: three space

用例3(这里输入为回车,输出也为空)
[in]:

[out]:

hasNext()

hasNext()会等待当前输入,和next()函数类似。

  1. 但是当hasNext()读到有效字符时,并且输入回车终止符后,hasNext()会返回true;
  2. 在未读到有效字符时,和next()一样,空格和回车都是无效字符。
1
2
3
4
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
System.out.println("输入的数据为:" + scan.hasNext());
}

可以看到下面的代码,while会一直运行,hasNext()一直等待下一个输入,当有输入有效字符并回车后,hasNext()返回true,循环体内scan.next()读到输入的字符。当输入一个数据并回车后(例如3),scan.next()会读取3,随后hasNext()读到\n,这时\n时无效字符,所以hasNext()等待有效字符的输入,停在判断语句处。

1
2
3
4
5
6
7
8
9
10
11
12
import java.util.Scanner;

public class test {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);

// 判断是否还有输入
while(scan.hasNext()) {
System.out.println("输入的数据为:" + scan.next());
}
}
}

hasNextLine()

hasNextLine()会等待当前输入,具体机制是通过正则表达式匹配当前位置及之后的字符。

  1. 当匹配到时,例如当前位置为\n,或者当前位置为有效字符,但有效字符后由结束符(\n或\r或\n\r或\r\n),hasNextLine()会返回true;
  2. 当匹配不到时,会返回false。
  3. 当匹配到有效字符,但是没有结束符,会一直等待输入结束符
1
2
3
private static final String LINE_SEPARATOR_PATTERN =
"\r\n|[\n\r\u2028\u2029\u0085]";
private static final String LINE_PATTERN = ".*("+LINE_SEPARATOR_PATTERN+")|.+$";

hasNextInt()

这个函数和hasNext()类似,多了判断读到的值是不是int的逻辑,如果当前读到的是空格或者回车,函数不会停止,仍然等待输入有效字符,直到输入数字或非数字,当输入非数字时,hasNextInt返回false,循环结束。

1
2
3
4
5
6
7
8
9
10
11
public class test {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);

// 判断是否还有输入
while(scan.hasNextInt()) {
int number = scan.nextInt();
System.out.println("输入的数据为:" + number);
}
}
}

关于牛客网和IDEA的实验结果不同的原因

以牛客OJ在线编程常见输入输出练习场中的A题目为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
题目描述:计算a+b

输入描述:输入包括两个正整数a,b(1 <= a, b <= 10^9),输入数据包括多组。

输出描述: 输出a+b的结果

示例:
输入:
1 5
10 20
输出:
6
30

hasNext() IDEA A题

采用复制粘贴形式,将牛课上用例粘贴到IDEA控制台,用例如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.util.Scanner;

public class Main{
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
while (scan.hasNext()) {
int a = scan.nextInt();
int b = scan.nextInt();
System.out.println(a+b);
}
}
}

用例:
1 5
10 20
scan.buf:"1 5\n10 20"

可以看到直接粘贴是没有最后的\n,如果自己一行一行敲进去会有\n。

运行到第二次int b时,会等待输入结束符回车,输入回车也就是\n后,buf变为”1 5\n10 20\n”,并且开始继续运行第三次循环你,而这时的hasNext()会一直等待输入,卡在第三次循环判断处。

hasNext() 牛客 A题

采用hasNext(),代码和上面IDEA实验的一样

20210317153915

  1. 如下图,采用hasNext(),调试时可以看到scan读到的是”1 5 10 20 “(没有”\n”,在IDEA上自己输入会有”\n”,但是20后面有一个空字符,不清楚是什么),并且position为0,代表当前要读的字符位置为0,即第一个字符。

20210317154043

  1. 如下图,当第一次读完a和b时,已经将1和5读入了scan.nextInt(),这时下一个要读的字符的位置为3,即第四个字符(空格算一个字符)

20210317154101

  1. 如下图,当第二次执行到while的判断语句时,position为3,这时hasNext()可以判定还有字符,返回true,于是进入循环体

20210317154321

  1. 如下图,当第二次读完a后,position变为6,是10后面那个空格的位置,即第七个字符,当然下一次读20时,空格会省去,这是next()的特点,可以看后文next()函数的详细介绍。

20210317154350

  1. 如下图,当第二次读完a和b后,position变为9,而9的位置刚好在20后面,这里猜想有两种可能:

1)猜想1:牛客网上用例输入系统后,没有回车\n这种符号(IDEA上面有”\n”),于是第三次循环时,hasNext()函数会返回false,也就不会进行第三次循环,而是执行到函数末尾了。那么position为9的数据是什么?如果是空格那hasNext()遇见空格会继续等待输入,也就不是空格;那如果是回车,就变成了猜想2

2)猜想2:牛客网上每一行结尾都有一个\n,但是调式系统没有显示,hasNext()碰到\n时认为没有接收到有效字符,接着往后面读,进而读到了后面一行的数字;当整个用例文件被读完的时候,按照hasNext()函数的特点,会一直等待继续输入,但是因为文件被读完,可能发出了文件结束的信号,从而程序运行结束。

20210317154513

因此更相信猜想2,于是得出以下结论:

结论:hasNext()运行一次,在未读到有效字符时,空格和回车都是无效字符;在读到了有效字符后,读到\n就执行完了当前这句代码,没有读到\n的话,会一直等待输入,知道输入终止符\n,hasNext()才会停止。牛客网上用例中,可能每一行结尾都有一个\n,而牛客网上的next和nextLine类函数可以停止等待输入,可能是文件读取结束发出信号所致(猜想)。

1
2
3
4
5
6
7
8
9
10
11
这个代码框内为最开始的想法,现在看来有误,只是保留在此

这个写在最前面,防止掉入陷阱。牛客网的判题系统的所有测试用例都保存在文件中,而文件中每一行末尾没有"\n",自己在IDEA的控制台输入时,会读到"\n",这就导致了在循环或者判断时,hasNext()和hasNextLine()在不同平台上结果会有差异。(可以自己在牛客和IDEA调试一下,看看scan对象的buf变量就知道了)

hasNext()对于没有\n的一行,判false

hasNextLine()对于没有\n的一行,判true (假定牛课上每一行结尾没有\n,那么无论有没有\n都会判true)(如果牛客上每一行结尾都有\n的话,那么hasNextLine()是判true,从而进循环体报错,没有\n会等待输入结束符。)

hasNext()对于有\n的一行,判true,无限循环

hasNextLine()对于有\n的一行,判true,无限循环

hasNextLine() IDEA A题

现在,在IDEA上测试下hasNextLine(),代码和用例如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.util.Scanner;

public class A {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);

while (scan.hasNextLine()) {
int a = scan.nextInt();
int b = scan.nextInt();
System.out.println(a+b);
}
}
}

用例:
1 5
10 20
scan.buf:"1 5\n10 20"

用例直接粘贴到控制台内,可以看到buf中为”1 5\n10 20”

20210317161319

当第一次读完a和b时,position变为3,指向第一个\n

20210317161915

当第二次运行完while的判断语句后,可以看到position仍未3,但是hasNextPattren匹配到了后面还有信息

20210317161945

当执行到第二次b的读入时,程序卡住了,等待输入,这时输入回车,就可以继续读入b的值

20210317162349

敲入回车后,看到position为9,buf也变为”1 5\n10 20\n”,这时9指向”\n”

20210317162540

在继续执行后,hasNextLine()仍然判断到后面有信息,进入循环体内。

20210317162730

这时执行到a的读入,nextInt()开始等待输入,程序变得无法结束

20210317162841

hasNextLine() 牛客 A题

现在,再使用hasNextLine()调试一下,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
import java.util.Scanner;

public class Main{
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);

while (scan.hasNextLine()) {
int a = scan.nextInt();
int b = scan.nextInt();
System.out.println(a+b);
}
}
}
  1. 如下图,当即将执行到while的判断语句hasNextLine()时,scan并没有读取到任何东西,当执行hasNextLine()后,才会读取输入,并判断是否有一行数据

20210316200122

  1. 如下图,当执行完hasNextLine()后,进入循环体,还未执行a这行代码。可以看到buf是”1 5 10 20 “,hasNextLine()以此判断有数据,返回true,才进入了循环体。

20210316200302

  1. 如下图,当即将执行到while的判断语句hasNextLine()时,position为9,已经指向空了,但是hasNextLine()在这里会返回true,判断机制还没有搞清楚,因此当执行到循环体第一句时,就会报错Exception occurred: java.util.NoSuchElementException (uncaught)Signal Dispatcher:

20210316210955

  1. 由此猜想牛客对用例的输入可能采取了某些方法,当然在只有牛客用例的时候,无法进行控制台输入,也就和上面IDEA的结果不同,IDEA是阻塞等待输入,而牛客这里直接报错。

  2. 当我们采用nextLine(),代码如下,读取这一行,相当于跳过当前行,这样下一次while判断时,hasNextLine()就不会返回true了,成功解决A题目。但是仅仅是输入行中没有”\n”时才可以用nextLine()来辅助hasNextLine(),当有”\n”时hasNextLine()永远返回true,无法跳出循环。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import java.util.Scanner;

public class Main{
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);

while (scan.hasNextLine()) {
int a = scan.nextInt();
int b = scan.nextInt();
System.out.println(a+b);
scan.nextLine();
}
}
}
  1. 看一下nextLine改变了什么,当执行nextLine之前,position为3,就是数字5后面的字符

20210317163748

  1. 当执行nextLine后,position为4,猜想当hasNextLine()遇见position为3的”\n”时,会返回true,而遇见position为4的”1”时,继续判断这一句,发现后面有”\n”,匹配成功,返回true。(假设牛客上的用例,每句末尾都有\n)

20210317163918

  1. 但是,当运行到第二次循环时,nextLine()将position变为10,见下面两张图,hasNextLine()在第三次判断时,没有\n字符了,甚至什么也没有,也就返回false,程序结束。
    20210317164208

20210317164221

hasNextLine() IDEA G题

再看一个hasNextLine()的实验,在IDEA上实验题目G,输入用例1,发现结尾没有\n,hasNextLine()会等待输入,知道等到\n,当输入\n后,这个\n前面的这行0都被读入,但是接下来hasNextLine()仍然会等待\n,从而陷入无限循环;当输入用例2时,IDEA会正常通过三行hasNextLine(),到达第四次循环并等待输入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.util.Scanner;

public class G {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);

while (scan.hasNextLine()) { //当前代码可以在牛客上通过
int result = 0;
String s = scan.nextLine();
Scanner rowIn = new Scanner(s);
while (rowIn.hasNextInt()) {
result += rowIn.nextInt();
}
System.out.println(result);
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
用例1:
1 2 3
4 5
0 0 0 0 0
用例1在IDEA中变量显示:"1 2 3\n4 5\n0 0 0 0 0"
用例1在牛客中变量显示:"1 2 34 50 0 0 0 0"
----
用例2:
1 2 3
4 5
0 0 0 0 0

用例2在IDEA中变量显示:"1 2 3\n4 5\n0 0 0 0 0\n"

hasNextLine() 牛客 G题

代码正常运行,因为有一句nextLine(),因此程序正常结束

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.util.Scanner;

public class G {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);

while (scan.hasNextLine()) { //当前代码可以在牛客上通过
int result = 0;
String s = scan.nextLine();
Scanner rowIn = new Scanner(s);
while (rowIn.hasNextInt()) {
result += rowIn.nextInt();
}
System.out.println(result);
}
}
}

综上,在使用Scanner进行输入输出时,尽量避免使用hasNextLine()。以下参考在查阅资料时给了很大启发,附在下面。(但是有些函数在牛客可以结束,但是在IDEA就无线等待输入还是没搞清楚)

https://www.nowcoder.com/discuss/8050
https://blog.csdn.net/weixin_36242811/article/details/105463062

Conda安装本地包

在下载包时,因为网络问题,无法下载,报告以下代码的错误,可以看到是网络连接错误,提示重试,如果一直无法稳定的下载好这个包的时候,可以通过其他工具下载以下网址的包。

1
2
3
4
5
CondaError: CondaHTTPError: HTTP 000 CONNECTION FAILED for url <https://conda.anaconda.org/menpo/linux-64/boost-1.59.0-py27_0.tar.bz2>
Elapsed: -

An HTTP error occurred when trying to retrieve this URL.
HTTP errors are often intermittent, and a simple retry will get you on your way.

以上面报错中的boost为例,我们可以使用迅雷下载上面错误中得到的这个地址:

1
https://conda.anaconda.org/menpo/linux-64/boost-1.59.0-py27_0.tar.bz2  

之后在这个包存放的目录下,切换到自己的环境中,使用以下命令安装本地包:

1
conda install --use-local  boost-1.59.0-py27_0.tar.bz2

清华源

官网:https://mirror.tuna.tsinghua.edu.cn/help/anaconda/

Ubuntu系统可以使用如下命令修改用户目录下的 .condarc 文件

1
sudo gedit ~/.condarc

将如下命令添加到文件中即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
channels:
- defaults
show_channel_urls: true
channel_alias: https://mirrors.tuna.tsinghua.edu.cn/anaconda
default_channels:
- https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main
- https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free
- https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/r
- https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/pro
- https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/msys2
custom_channels:
conda-forge: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
msys2: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
bioconda: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
menpo: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
pytorch: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
simpleitk: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud

运行 conda clean -i 清除索引缓存,保证用的是镜像站提供的索引。

运行 conda create -n myenv numpy 测试一下吧。

创建环境

创建一个名为envirment_name的虚拟环境

1
conda create -n envirment_name

激活环境

1
conda activate envirment_name

安装包

安装3.6版本的python

1
conda install python==3.6

删除包

1
conda uninstall python

删除环境

删除envirment_name这个环境的所有内容,-n指定环境名,–all代表删除全部内容。

1
conda remove -n envirment_name --all

使用yml创建环境

conda可以通过config文件指定包的版本来创建环境,例如使用如下命令

1
conda env create --file environment.yml

其中environment.yml文件中的信息如下

1
2
3
4
5
6
7
8
9
10
11
12
13
name: envirment_name
channels:
- defaults
- conda-forge
- pytorch
dependencies: # everything under this, installed by conda
- python=3.6
- pip
- pytorch=1.2.0
- torchvision=0.4.0
- cudatoolkit=10.0
- pip: # everything under this, installed by pip
- open3d==0.8.0

清理conda无用的包和缓存

cache目录在/home/username/.conda/pkgs。可以使用-p来删除没有用到的包,想删除干净点就用-y –all来删除所有下载的包和缓存。想看更多选项可以-h看帮助文档。

1
2
3
conda clean -p      //删除没有用的包
conda clean -y -all //删除所有的安装包及cache
conda clean -i //删除索引缓存

Virtual Multi-view Fusion for 3D Semantic Segmentation

论文:https://arxiv.org/abs/2007.13138
会议:ECCV 2020

总结

本文在点云分割领域对多视图方法进行了研究,提出了虚拟视图的概念,避免了真实相机的局限性,提出了新的多视图方法,在scannet和s3dis上效果较多视图方法有很大的提升,但是没看到论文中对具体分割方法的介绍,可能没用到3d分割网络?该文章的代码还没有公开。

动机

体素方法缺点:
用于3D语义分割的最新技术(SOTA)方法使用3D稀疏体素卷积运算符来处理输入数据。例如,MinkowskiNet和SparseConvNet 各自将输入数据加载到稀疏3D体素网格中,并使用稀疏3D卷积提取特征。这些“placecentric”的方法旨在识别3D模式,因此对于具有独特3D形状的对象类型(例如椅子)效果很好,而对其他对象(例如墙面图片)效果不佳。它们还占用了大量内存,这限制了空间分辨率和/或批处理大小。

多视图方法缺点:
当可获得posed RGB-D图像时,尝试使用为处理摄影RGB图像而设计的2D网络预测密集的特征和/或语义标签,然后将它们聚集在可见的3D表面上,或者将特征投影到可见的表面上,并在3D中将它们进一步卷积。
尽管这些“view-centric”的方法利用了在大型RGB图像数据集上进行预训练的大规模图像处理网络,但由于在RGB-D扫描数据集中存在遮挡,光照变化和相机姿态未对准的困难,因此无法在标准3D分割基准上达到SOTA性能。在ScanNet基准测试的3D语义标签挑战赛中,基于视图的方法目前不在当前排行榜的上半部分。

贡献

本文提出了一种新的基于视图的3D语义分割方法,该方法克服了先前方法的问题。关键思想是使用从3D场景的“虚拟视图”渲染的合成图像,而不是将处理限制为由物理相机获取的原始摄影图像。
这种方法有几个优点,可以解决以前以视图为中心的方法遇到的关键问题。

  1. 我们可以对非自然视场的虚拟视图选择camera intrinsics,以增加在每个渲染图像中观察到的上下文。
  2. 我们在与场景表面的距离/角度变化较小、物体之间遮挡相对较少、表面覆盖冗余较大的位置选择虚拟视点。
  3. 我们渲染非真实的图像,没有依赖于视图的光照效果和背对表面的遮挡——也就是说,虚拟视图可以从墙壁、地板和天花板后面看到一个场景,以提供相对大的背景和小的遮挡。
  4. 我们根据已知的虚拟视图的摄像机参数将像素预测聚合到三维表面上,这样就不会在咬合轮廓上遇到语义标签的“溢出”。
  5. 在训练和推理过程中的虚拟视图可以模拟多尺度的训练和测试,避免二维cnn的尺度内方差问题。在培训和测试期间,我们可以生成任意数量的虚拟视图。在训练期间,由于数据的增加,更多的虚拟视图提供了健壮性。在测试期间,由于投票冗余,更多的视图提供了健壮性。
  6. 我们的多视点融合方法中的二维分割模型可以受益于大型图像预处理数据,如ImageNet和COCO,这些数据对于纯三维卷积方法是不可用的。

方法

总体流程

Training stage.
在训练阶段,首先为每个3D场景选择虚拟视图,然后为每个虚拟视图选择摄影机内在,摄影机外部,要渲染的通道以及渲染参数(例如,深度范围,背面剔除).
然后,通过为所选通道和groudtruth语义标签渲染所选虚拟视图来生成训练数据。
使用渲染的训练数据训练2D语义分割模型,并在推理阶段使用该模型。
Inference stage.
在推理阶段,使用与训练阶段类似的方法来选择并渲染虚拟视图,但是没有groudtruth语义标签。
使用训练好的模型在渲染的虚拟视图上进行2D语义分割,将2D语义特征投影到3D,然后通过融合多个投影的2D语义特征来导出3D中的语义类别。

虚拟视图选择

Camera intrinsics.

使用针孔摄像机模型,提高了FOV,得到的二维图像更大,提供了更大的上下文信息。

Camera extrinsics.

使用以下四种策略选择相机外参

  • 均匀采样:
    均匀采样相机外部,以生成许多新颖的视图,而与3D场景的特定结构无关。
    具体,使用3D场景顶部均匀采样位置的自顶向下视图,以及从场景中心看去但均匀采样位置的视图。
  • 尺度不变采样:
    由于2D卷积神经网络通常不是尺度不变的,因此如果视图的尺度与3D场景不匹配,则模型性能可能会受到影响。
    为了克服此限制,针对3D场景中的片段以一定比例尺对视图进行采样。
    具体来说,对3D场景进行了过度分割,对于每个片段,都将相机定位为通过沿法线方向拉回到一定范围的距离来观看片段。
    进行深度检查以避免前景物体的遮挡。如果在渲染阶段禁用了背面剔除,将进行光线跟踪并删除被背面遮挡的所有视图。
    3D场景的过度分割是不受监督的,并且不使用地面真实语义标签,因此尺度不变采样可以应用于训练和推理阶段。
  • 类平衡采样:
    类平衡已被广泛用作2D语义分段的数据增强方法。
    通过选择查看代表性不足的语义类别的网格段的视图来进行类平衡,类似于尺度不变采样方法。
    注意,这种采样方法仅适用于ground truth语义标签可用的训练阶段。
  • Original views sampling:
    从原始摄影机视图中进行采样,因为它们代表了人工如何在具有实际物理约束的真实3D场景中选择摄影机视图。
    此外,3D场景是从原始视图重建的,因此包含它们可以确保覆盖拐角情况,否则这些情况很难作为随机虚拟视图。

    Channels for rendering.

    渲染了以下通道:RGB color、normal、规范化全局XYZ坐标。额外的通道允许我们超越现有RGB-D传感器的限制。图5显示了虚拟视图渲染的通道。
    20201126164253

    Rendering parameters.

    在渲染视图时可打开背景剔除,这样相机视图不会被遮挡,例如一个场景中,我们既可以选择外部视图,也可以选择内部视图。如图6所示。
    20201126164535

    Training vs. inference stage.

    我们希望在训练和推理阶段使用类似的视图选择方法,以避免产生领域差距。虽然推理成本在现实世界的应用中可能很重要,但在本文中,考虑离线3D分割任务且不在任何一个阶段优化计算成本,所以论文在任何一个阶段使用任意多的虚拟视图。

Multiview Fusion

2D semantic segmentation model

用渲染的虚拟视图作为训练数据,训练一个2D语义分割模型。使用xception 65特征提取器和DeeplabV3+ 解码器。
从在ImageNet上训练的预先训练的分类模型检查点初始化模型。当使用附加输入通道(如正常图像和坐标图像)训练模型时,通过将权重平铺在附加通道上并在每个空间位置对其进行归一化来修改预训练检查点的第一层,使得沿着通道维度的权重总和保持不变。

3D fusion of 2D semantic features

在推理过程中,在虚拟视图上运行2D语义分割模型,并获得图像特征(例如,每个像素的一元概率)。为了将2D图像特征投影到3D,使用以下方法:

  1. 在虚拟视图上渲染深度通道;
  2. 对于每个3D点,将其投影回每个虚拟视图,并且仅当像素的深度与点到相机的距离匹配时,才累积投影像素的图像特征。
  3. 与从每个像素投射光线以找到要聚集的3D点的替代方法相比,该方法实现了更好的计算效率。
  4. 首先,场景中3D点的数量远小于场景的所有渲染图像中的像素总数。
  5. 其次,使用深度检查投影3D点比涉及光线投射的操作更快。
    20201126170300
    20201126170319

Experiments

Scannet

20201126170347

20201126170642

20201126170359

S3DIS

20201126170415

20201126170447

Effect of Training Set Size and number of views at Inference

20201126170832

结论

本文提出了一种虚拟多视角融合的纹理meshes三维语义分割方法。这种方法引入了几个显著提高标签性能的新思想:带有附加通道的虚拟视图、背面剔除、宽视野、多尺度感知视图采样。因此,它克服了困扰大多数以前多视角融合方法的2D-3D错位、遮挡、窄视角和尺度不变性问题。
本文得出的令人惊讶的结论是,多视图融合算法是3D纹理网格语义分割的3D卷积的可行替代方案。虽然这项任务的早期工作考虑了多视图融合,但近年来,通用方法已被放弃,取而代之的是点云的3D卷积和稀疏体素网格。本文表明,仔细选择和渲染虚拟视图的简单方法使得多视图融合能够优于几乎所有最近的3D卷积网络。

bash中文乱码

将编码调为utf8

jupyter在bash界面中文乱码

将编码调为GBK

上面两个矛盾,还没发现解决方法

python os.path用法

导入方法

1
import os

os.path.dirname

1
2
3
4
5
6
7
8
9
# os.path.dirname 返回文件路径,不包括文件名
file1_dir = os.path.dirname("D:\\6_code\pycharm\download.py")
file2_dir = os.path.dirname("D:\\6_code\\pycharm\\download.py")
file3_dir = os.path.dirname("D:/6_code/pycharm/download.py")
file4_dir = os.path.dirname("D://6_code//pycharm//download.py")
print(file1_dir)
print(file2_dir)
print(file3_dir)
print(file4_dir)
out:
D:\6_code\pycharm
D:\6_code\pycharm
D:/6_code/pycharm
D://6_code//pycharm

os.path.dirname(file)

os.path.dirname(file)返回脚本的路径,但是需要注意一下几点:

1、必须是实际存在的.py文件,如果在命令行执行,则会引发异常NameError: name ‘file‘ is not defined

2、在运行的时候如果输入完整的执行的路径,则返回.py文件的全路径如:

python c:/test/test.py 则返回路径 c:/test ,如果是python test.py 则返回空

3、结合os.path.abspath用,效果会好,如果大家看过一些python架构的代码的话,会发现经常有这样的组合

os.path.dirname(os.path.abspath(file)),os.path.abspath(file)返回的是.py文件的绝对路径

这就是os.path.dirname(file)的用法,其主要总结起来有:
1、不要已命令行的形式来进行os.path.dirname(file)这种形式来使用这个函数

2、结合os.path.abspath()使用

os.path.abspath

1
2
3
4
5
# os.path.abspath 返回绝对路径
abs_dir = os.path.abspath(os.path.dirname("D://6_code//pycharm/download.py"))
print(abs_dir)
abs2_dir = os.path.abspath("D://6_code//pycharm/download.py")
print(abs2_dir)
out:
D:\6_code\pycharm
D:\6_code\pycharm\download.py

返回上一层目录

1
2
3
4
5
# 在路径后加上'/..’相当于返回上一层目录
test = os.path.dirname("D://6_code//pycharm/download-scannet.py") + '/..'
test1 = os.path.abspath(os.path.dirname("D://6_code//pycharm/download-scannet.py") + '/..')
print(test)
print(test1)
out:
D://6_code//pycharm/..
D:\6_code

os.path.split

1
2
3
4
5
6
# os.path.split将路径和文件分开
file_split = osp.split('D://6_code//pycharm/download-scannet.py')
print(file_split)
# os.path.splitext将文件及路径和扩展名分开
file_splitext = osp.splitext('D://6_code//pycharm/download-scannet.py')
print(file_splitext)
out:
('D://6_code//pycharm', 'download-scannet.py')
('D://6_code//pycharm/download-scannet', '.py')

python glob模块的用法

介绍

glob是python自带的一个操作文件的模块。用glob可以查找符合规定的文件路径名,可用以下匹配符

1
2
3
4
5
* 匹配0个或多个字符;
** 匹配所有文件,目录,子目录和子目录里面的文件 (3.5版本新增)
? 匹配单个字符;
[] 匹配指定范围内的字符,如:[0-9]匹配数字。
[!] 匹配不在指定范围内的字符

示例

假设windows中有如下一个目录,其中_config.yml和.gitgnore是文件,其他是文件夹,每个dir文件夹中有一个diri.py文件,例如dir2中有一个dir2.py文件。

1
2
3
4
5
6
7
8
9
10
.
├── example
├── dir1
├── dir2
├── dir3
├── train
├── val
├── test
├── _config.yml
└── exp.py

遍历当前目录

1
2
3
4
print(glob.glob('./*'))

out:
['.\\example', '.\\exp.py', '.\\test', '.\\train', '.\\val', '.\\_config.yml']

遍历上一级目录(在example文件夹中)

1
2
3
4
print(glob.glob('../*'))

out:
['.\\example', '.\\exp.py', '.\\test', '.\\train', '.\\val', '.\\_config.yml']

遍历本级下文件

1
2
3
4
5
6
print(glob.glob('./*.*')) #本级下所有文件
print(glob.glob("./*.yml")) #本级下所有yml文件

out:
['.\\exp.py', '.\\_config.yml']
['.\\_config.yml']

匹配特定文件目录

1
2
3
4
5
6
7
print(glob.glob("./*[ca][nf]*")) # 匹配ca和nf字母组合但一定相邻的名字
print(glob.glob("./*[ca]*[nf]*")) # 匹配有ca和nf中任意字母组合的名字
print(glob.glob("./*e?p*")) # 匹配e和p中间只有一个字符的名字
out:
[]
['.\\train', '.\\_config.yml']
['.\\exp.py']

匹配子目录及文件

1
2
3
4
5
6
print(glob.glob("./example/*")) # 遍历example文件夹中所有内容
print(glob.glob("./example/*/*")) # 遍历example文件夹中所有子文件夹及子文件夹中的文件

out:
['./example\\dir1', './example\\dir2', './example\\dir3']
['./example\\dir1\\dir1.py', './example\\dir2\\dir2.py', './example\\dir3\\dir3.py']