Zhangzhe's Blog

The projection of my life.

0%

TL;DR

  • 本文介绍了 YOLO 系列几种高效的 backbone 设计,主要包括:VoVNetPRNCSPNetELANE-ELAN

Algorithm

1. VoVNet

  • paper: https://arxiv.org/pdf/1904.09730.pdf
    vovnet.png
  • 作者认为 densenet 存在问题:每一层 Conv 都使用之前所有层的输出,因此会导致当前 Convinput channel 很大,输出到 output channel 却较小
  • 因此,作者只在 VoVNet Block 的最后一个 Conv 才用之前所有层的输出
  • 相同计算量下,效果优于 ResnetDenseNet

2. PRN

3. CSPNet(YOLOV5)

4. ELAN

  • paper: https://arxiv.org/pdf/2211.04800.pdf
    elan.png
  • ELAN 全称是 Efficient Layer Aggregation Network, 作者以 VoVNetResNet 做对比,VoVNet 在叠加更多 block 时表现要比 ResNet 更差,作者分析是因为 VoVNet 结构中存在过多的 transition layers,这导致在叠加 block 时最短梯度路径( the shortest gradient path )不断增加,从而使得 block 增加时训练难度上升
  • PRN 相比 ResNet,使用 mask 让输入只有部分 channel 通过 identity connection,丰富了梯度来源;
  • CSPNet 通过将 channel split,一方面增加了梯度信息(同 PRN),另一方面减少了 computational block 中的计算量;
  • ELAN 的思想是:搭建网络时需要考虑每一层的最短最长梯度路径,还要考虑整个网络的最长梯度路径。

5. E-ELAN(YOLOV7)

URL

TL;DR

  • 本文提出一种以自动驾驶规划为目的的神经网络架构,该架构对每个感知子任务显式监督,合理的将子任务连接起来,增加了子任务之间的协调性,并增加了模型的可解释性

WIP

URL

TL;DR

  • 使用预测下一个词(语言建模 language modeling)任务进行自监督预训练
  • 预训练的模型需要使用 reinforement learning with human feedback(RLHF) 进行对齐(align),这个过程不会在测试数据集上提高模型表现,但可以更好的对齐人类的意图和三观
  • 模型输入可以是图片和文本,输出为文本

Details

  • 使用了很强大的基建,可以做到准确预测模型训练的最终效果(scaling),可以以较小的代价和较快的时间找到最合适的模型架构和超参数设置
  • 为模型引入了 steerability(操纵性),可以在模型的 prompt 中加入一些 System message,让模型回复风格拥有某种特质(比如老师、政客等)
  • GPT-4 使用了很多机制提高了模型的安全性

Thought

  • 这篇技术报告更多是对模型效果的分析,基本没有模型细节的描述
  • 大模型逐渐变成大厂垄断,普通研究者能摸到的最后只剩下一个 API

URL

TL;DR

  • 作者团队训练了一个 96Transformer1750 亿参数的超大模型(GPT2 只有约 15 亿参数),在下游任务上无需 fine-tuning 即可得到很好的效果。
  • 本质是 GPT2 的放大版(参数量放大了一百多倍)

Algorithm

  • 在下游任务上,可以使用 Zero ShotOne ShotFew Shot 三种方式推理模型,下图以英语翻译法语的例子介绍三者的区别:
    GPT3.png
  • GPT3 系列模型详细设置:
    GP3_1.png
  • GPT3 自监督训练数据:
    GP3_2.png

使用了 common crawl 数据集,由于 common crawl 数据集很脏,所以训练是数据采样率并不高

  • 下图是在几个下游任务上和 SOTA 算法的比较:
    GPT3_3.png
    GPT3_5.png
    GPT3_4.png

从普遍表现看,GPT3 few shot 效果 > one shot > zero shot,不一定比 SOTA 点高(SOTA 普遍使用了 fine tuning,直接比较不公平)

Thought

  • 在某些任务上,GPT3 few shot 效果可媲美 fine tuning SOTA,可以说明 GPT3 还是非常强大的
  • 比上一代参数量提高一百多倍,开启了大模型时代…

URL

TL;DR

  • 本文提出一种类似 chatGPT 的交互式 Zero-shot 分割算法,用户给出一个 prompt(支持 point / box / mask / text),模型会根据 Prompt 语义完成分割,无需在特定分割任务数据上 fine-tuning(类似于 GPT2 和之后的系列模型)
  • 本文一个非常重要的理念是 Data Centric,即以数据为中心而不是以模型为中心,这一点和 GPT 系列也不谋而合
    • 传统视觉算法是在固定的数据集上修改模型结构实现模型效果的提升,实际是以模型为中心
    • 数据为中心的算法通常固定模型结构,通过例如 RLHF(reinforcement learning from human feedback) 的方法,使用模型辅助标注员高效标注大量数据(11 亿个 mask 区域),重复迭代提高效果
  • 模型本身由三部分组成:
    • image_encoder:提取图片特征,使用的是 ViT 模型,在交互过程中只需要推理一次
    • prompt_encoder: 提取 prompt 特征,将人的输入(例如点或框)编码到特征空间
    • mask_decoder: 输入为图片特征和 prompt 特征,融合后输出分割 mask

Algorithm

任务定义

sam3.png

理论上支持 point / box / mask / text,但 demo 和 code 都只包含了 point / box / mask

模型结构

sam4.png

image encoder 是 VIT
prompt encoder 对于 box / point prompt 只是简单的位置编码;对于 mask 是几层简单的卷积
lightweight mask decoder 是轻量级 transformer

Data centric

sam2.png

模型为中心和数据为中心的对比

使用效果

sam1.png

图中的框为用户输入的 prompt,模型会根据 prompt 输出分割结果

Thought

  • Data centric 感觉一定是未来,但形式一定不会以 RLHF 形式存在,而更多的以自监督形式存在
  • prompt 未来会取代 fine-tuning 这个词

URL

TL;DR

  • Bert 全方位打败 GPT 之后,OpenAI 推出了参数量更大的 GPT2
  • GPT2 与之前所有的 NLP 预训练模型使用的 自监督预训练 + 任务相关 fine-tuning 范式不同,GPT2 不再需要任何数据相关 fine-tuning,而是使用 prompt(提示词)
  • prompt(提示词) 是一段在模型推理阶段用于描述任务的文本,通常加在问题之前,起到提示模型的作用

Algorithm

GPT2 很大程度上只是 GPT 的放大版,所引入的创新并不多

  • 使用了 Byte Pair Encoding(BPE) 分词算法,本质是一种贪心算法
  • 由于自回归(auto-regression)模型推理容易出现死循环,所以本文提出一种 top-k 输出按 softmax 概率采样的 trick,增加模型的随机性

Thought

  • GPT2 最重要的作用是提出了使用 Prompt 替代 fine-tuning 的范式,为之后的 AIGC 大面积推广扫平了障碍

URL

TL;DR

  • 本文基于 FCOS 论文提出一种架构简单的 Anchor Free 的单目 3D 检测算法 FCOS3D,在 NeurIPS 2020nuScenes 3D 检测比赛纯视觉赛道上取得了第一名。

Algorithm

问题定义

  • 本文提出的 FCOS3D 要解决的核心问题是一个 图片到 7-DoF 属性(x, y, z, w, l, h, yaw+dir)的预测

    • DoF 是指 degree of freedom(自由度)。
    • (x, y, z, w, l, h, yaw+dir) 分别表示物体在相机坐标系下的 3 维坐标和长宽高(单位都是米),和偏航角(俯视图角度,单位是弧度)和方向 2 分类共同构成朝向。
  • 对于 nuScenes 3D 检测比赛,还需要解决的非核心问题包括:

    • 预测出的 3D 框物体的类别(10类物体)
    • 预测出的 3D 框物体的属性(9种属性)
    • 预测出的 3D 框物体的 x, y 轴速度(不是 “病态” 问题了,已经属于癌症问题了…)

网络结构

fcos3d.png

  • backboneFPN 比较常规

  • decode headFCOS 一样,使用了不同 level feature 的参数共享(反正是全卷积,不存在 shape 问题)

  • decode head 中包括:

    • 分类分支:

      • class: output_shape = (N, 10, H, W),使用 FocalLoss
      • attribute: output_shape = (N, 9, H, W),使用 CrossEntropyLoss
    • 回归分支:

      • box: output_shape = (N, 9, H, W)(dx, dy, log(z), log(w), log(l), log(h), yaw, vx, vy),使用 SmoothL1Loss
      • centerness: output_shape = (N, 1, H, W),使用 BCEWithLogitsLoss
      • direction: output_shape = (N, 2, H, W),使用 CrossEntropyLoss

target 设置

  • target 设置中使用了很多 2D 引导 3D 的思想。

(x, y, z) target 设置

  • 由于是 3D 检测,所以 GT 的 3D 框坐标(x, y, z, w, l, h)单位都是米,这对神经网络是不友好的(因为神经网络看到的是像素,预测以像素为单位更容易)。
  • 因此,本文实际是一个 2.5D 的预测(xy 2D, z 3D),实际预测的 x, y 是像素坐标系下相对于 feature map 每一个点的偏移量(由相机坐标系和相机内参可计算得到像素坐标系),z, w, l, h 的预测是相机坐标系下的米为单位的真值取 log

centerness target 设置

  • FCOS 不同,FCOS3D centerness: c=eα((Δx)2+(Δy)2)c = e^{-\alpha((\Delta x)^2+(\Delta y)^2)}α=2.5\alpha=2.5

yaw target 设置

  • 本文将 yaw (0 ~ 2π2\pi)编码成 yaw (0 ~ π\pi)和方向

正样本选择

  • FCOS 是将 feature map 上的每个位置到 GT 中心点的距离小于 1.5 * stride 的点作为正样本。
  • FCOS3D3D 检测,没办法直接使用 FCOS 提出的方法;解决方法和 x, y 坐标回归方法类似,如果 2.5D 坐标下的 x, yfeature map 位置距离小于 1.5 * stride,则算作正例。

GT 尺度分配

  • FCOS 思想一样

不同尺度 feature map 缩放

  • FCOS 思想一样,只是更丰富 scale.shape == (num_of_level, 3),分别表示 scale_offset(for xy) / scale_depth(for z) / scale_size(for wlh)

Thought

  • 本文极大程度的借鉴了 FCOS,相当于 FCOS2.5D
  • 加入了很多 trick: log(z), centerness target 定义,encode yaw 等,很 work

URL

TL;DR

  • Faster RCNN 系列、SSDYOLOv2~v5(注意 YOLOv1 不包括在内)都是基于 Anchor 进行预测的。
  • 本文提出一种 Anchor Free 的 one stage 目标检测方法,整个模型结构非常轻量,效果强大。
  • 由于没有了 anchor,所以 fcos 可方便拓展到其他任务。

Algorithm

网络结构

fcos.png

  • backbone + FPN 输出了 5 种尺度的 feature map 用于预测,由于是全卷积网络,所以 5 个输出头共享一份参数对于每个尺度的 feature map 上的每一个位置 预测包括类别(N,Cls,H,W)、框的位置(N,4,H,W)和一个中心置信度(N,1,H,W)。
  • 由于共享输出头,所以本文作者 为每个输出头增加了不共享的 scale 参数,scale.shape == (num_of_level, 1)
    fcos_2.png
  • 其中位置参数模型预测的是如上图所示的(l,t,b,r),即相对于 feature map 上的点到 GT 的上下左右偏移量。

centerness

  • centerness=min(l,r)max(l,r)×min(t,b)max(t,b)centerness=\sqrt{\frac{min(l^\star,r^\star)}{max(l^\star,r^\star)}\times \frac{min(t^\star,b^\star)}{max(t^\star,b^\star)}} ,即 GT bbox 内的点越靠近中心越大,越远离中心越小,取值范围 [0, 1],可视化 centerness 热力图如上图所示。
  • 最终预测时,score 阈值过滤的是 centerness * score

损失函数

L({px,y},{tx,y})=1Nposx,yLcls(px,y,cx,y)+λNposx,yIcx,y>0Lreg(tx,y,tx,y)+γNposx,yIcx,y>0Lctr(sx,y,sx,y)L(\{p_{x,y}\},\{t_{x,y}\})=\frac{1}{N_{pos}}\sum_{x,y}L_{cls}(p_{x,y},c^\star_{x,y})+\frac{\lambda}{N_{pos}}\sum_{x,y}\mathbb{I}_{c_{x,y}^\star>0}L_{reg}(t_{x,y},t^\star_{x,y})+\frac{\gamma}{N_{pos}}\sum_{x,y}\mathbb{I}_{c_{x,y}^\star>0}L_{ctr}(s_{x,y},s^\star_{x,y})

  • 其中 px,yp_{x,y} 表示在特征图点(x,y)处预测的每个类别的 score
  • cx,yc^\star_{x,y} 表示在特征图点(x,y)处的真实类别(负样本类别为 0
  • tx,yt_{x,y} 表示在特征图点(x,y)处预测的目标边界信息
  • sx,ys_{x,y} 表示在特征图点处预测的centerness
  • LclsL_{cls} 使用 focal loss 以平衡正负样本
  • LregL_{reg} 使用 GIOU loss且只对正样本计算
  • LctrL_{ctr} 使用 focal loss且只对正样本计算

正样本选择策略

  • anchor base 方法不同,fcos 对正样本的选择较为苛刻,仅当 feature map 上的某个点落入 gt bbox 中心区域(sub-box)时才被当做正样本
  • sub-box 的定义: (cxrs,cyrs,cx+rs,cy+rs)(c_x-rs,c_y-rs,c_x+rs,c_y+rs) ,其中 (cx,cy)(c_x,c_y) 表示 gt bbox 中心点在原始图上的坐标;s 表示 stride 即当前 feature map 相较于原图下采样倍数;r 表示 radius 半径超参数,在 coco 数据集上取 1.5
  • 除了正样本之外,其他样本的 cls 类别都被置为 0(background),负样本只计算 cls loss,不计算 reg losscenterness loss(也没法计算,有框才能计算)。

Ambiguous sample

  • anchor free 的检测方法绕不开一个天然的问题:如果一个 feature map 的特征点(x,y)同时是两个 GT bbox 的正例,应该如何预测,毕竟 fcos 每个特征点只预测一个框。
  • 本文缓解该问题的方法是:使用 FPN box 尺度分配 + center sampling
    • FPN bbox 尺度分配是一个常用的解决 Ambiguity 问题的方法,越大的 feature map 负责检测越小的框。(将 Ambiguity 出现的概率从 23.16% 降低到 7.24%
    • center sampling:即上面提到的 sub-box 采样方法,radius = 1.5。(将 Ambiguity 出现的概率从 7.24% 降低到 2.66%

Thought

  • FCOS 是一种很简单高效的 2D anchor free 物体检测算法,迁移性强,启发了后面的 FCOS3D 单目 3D 检测。

URL

TL;DR

  • 本文提出一种新的车道线检测范式,可以在极低的计算复杂度下精准预测车道线位置。
  • 与常见的使用语义分割算法实现车道线检测的范式不同,本文提出的车道线检测范式是将图片 ROI 区域分割成若干像素块,使用分类的方法判断像素块是否包含车道线。

Algorithm

算法思想

ufld.png

  • 将 ROI 区域(通常是一张图片的下半部分,上半部分是天空不包含车道线)分成若干 稀疏的行和稠密的列,论文给出的行数是 18 行 200 列
  • 模型预测每个小格子是否包含车道线,以及包含的车道线属于哪一个车道线实例(主流 benchmark 要求模型预测相邻的 4 条车道线:| |车| |)。
  • 对于 CULane 数据集,模型输出 shape == (N, 4, 18, 201),分别表示 18 行 200 列每个格子是否包含车道线(所以是 201 分类),以及包含的车道线的实例编号。
  • 加入了一个普通分割辅助任务头加速训练,推理时丢弃,不影响速度。
  • 另外除了分类交叉熵损失函数之外,本文加入了两个车道线相关的先验损失函数:
    • 基于车道线连续属性:每条车道线的第 i 行和第 i + 1 行应该具有相近的位置。

    • 基于车道线相对笔直属性:每条车道线点第 i 行和第 i + 1 行的连线应该和第 i + 1 行与第 i + 2 行的连线共线。

部署优化

  • 网络末尾使用的高维 FC 层对部署模型加速不利,使用 conv + pixelshuffle(depth to space) 可有效解决。

Thought

  • 辅助训练输出头是分割任务的标配
  • 结构先验损失函数貌似是个故事,作者开源代码中这两个 loss 的权重都是 0
  • 范式很好,可经过部署优化后上车

TL;DR

  • 《俄罗斯方块》这部电影里游戏作者用命令行玩俄罗斯方块原型机太酷了,所以决定自己实现一把
    tetris.png
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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
import numpy as np
from func_timeout import FunctionTimedOut, func_set_timeout
from copy import deepcopy
import keyboard
from random import choice
def random_choice_block():
blocks = [
(
# . . . . . . [x][x] . . . . . .
# . . . . . . [x][x] . . . . . .
(
[0, space.shape[1] // 2 - 1],
[0, space.shape[1] // 2],
[1, space.shape[1] // 2 - 1],
[1, space.shape[1] // 2],
),
),
(
# . . . . . . [x][x] . . . . . .
# . . . . . . . [x][x] . . . . .
(
[0, space.shape[1] // 2 - 2],
[0, space.shape[1] // 2 - 1],
[1, space.shape[1] // 2 - 1],
[1, space.shape[1] // 2],
),
# . . . . . . [x] . . . . . .
# . . . . . [x][x] . . . . . .
# . . . . . [x] . . . . . . .
(
[0, space.shape[1] // 2],
[1, space.shape[1] // 2 - 1],
[1, space.shape[1] // 2],
[2, space.shape[1] // 2 - 1],
),
),
(
# . . . . . . [x][x] . . . . . .
# . . . . . [x][x] . . . . . . .
(
[0, space.shape[1] // 2],
[0, space.shape[1] // 2 + 1],
[1, space.shape[1] // 2 - 1],
[1, space.shape[1] // 2],
),
# . . . . . [x] . . . . . .
# . . . . . [x][x] . . . . . .
# . . . . . . [x] . . . . . . .
(
[0, space.shape[1] // 2 - 1],
[1, space.shape[1] // 2 - 1],
[1, space.shape[1] // 2],
[2, space.shape[1] // 2],
),
),
(
# . . . . . . [x][x][x][x] . . . . . .
(
[0, space.shape[1] // 2 - 2],
[0, space.shape[1] // 2 - 1],
[0, space.shape[1] // 2],
[0, space.shape[1] // 2 + 1],
),
# . . . . . . [x] . . . . . .
# . . . . . . [x] . . . . . .
# . . . . . . [x] . . . . . .
# . . . . . . [x] . . . . . .
(
[0, space.shape[1] // 2],
[1, space.shape[1] // 2],
[2, space.shape[1] // 2],
[3, space.shape[1] // 2],
),
),
(
# . . . . . . [x] . . . . . .
# . . . . . [x][x][x] . . . . .
(
[0, space.shape[1] // 2],
[1, space.shape[1] // 2 - 1],
[1, space.shape[1] // 2],
[1, space.shape[1] // 2 + 1],
),
# . . . . . . [x] . . . . . .
# . . . . . . [x][x] . . . . .
# . . . . . . [x] . . . . . .
(
[0, space.shape[1] // 2 - 1],
[1, space.shape[1] // 2 - 1],
[1, space.shape[1] // 2],
[2, space.shape[1] // 2 - 1],
),
# . . . . . [x][x][x] . . . . .
# . . . . . . [x] . . . . . .
(
[0, space.shape[1] // 2 - 1],
[0, space.shape[1] // 2],
[0, space.shape[1] // 2 + 1],
[1, space.shape[1] // 2],
),
# . . . . . . [x] . . . . . .
# . . . . . [x][x] . . . . .
# . . . . . . [x] . . . . . .
(
[0, space.shape[1] // 2],
[1, space.shape[1] // 2 - 1],
[1, space.shape[1] // 2],
[2, space.shape[1] // 2],
),
),
(
# . . . . . [x] . . . . . . .
# . . . . . [x][x][x] . . . . .
(
[0, space.shape[1] // 2 - 1],
[1, space.shape[1] // 2 - 1],
[1, space.shape[1] // 2],
[1, space.shape[1] // 2 + 1],
),
# . . . . . [x][x] . . . . . .
# . . . . . [x] . . . . . . .
# . . . . . [x] . . . . . . .
(
[0, space.shape[1] // 2 - 1],
[0, space.shape[1] // 2],
[1, space.shape[1] // 2 - 1],
[2, space.shape[1] // 2 - 1],
),
# . . . . . [x][x][x] . . . . .
# . . . . . . . [x] . . . . .
(
[0, space.shape[1] // 2 - 1],
[0, space.shape[1] // 2],
[0, space.shape[1] // 2 + 1],
[1, space.shape[1] // 2 + 1],
),
# . . . . . [x] . . . . . . .
# . . . . . [x] . . . . . . .
# . . . . [x][x] . . . . . . .
(
[0, space.shape[1] // 2],
[1, space.shape[1] // 2],
[2, space.shape[1] // 2 - 1],
[2, space.shape[1] // 2],
),
),
(
# . . . . . . . [x] . . . . .
# . . . . . [x][x][x] . . . . .
(
[0, space.shape[1] // 2 + 1],
[1, space.shape[1] // 2 - 1],
[1, space.shape[1] // 2],
[1, space.shape[1] // 2 + 1],
),
# . . . . . [x] . . . . . . .
# . . . . . [x] . . . . . . .
# . . . . . [x][x] . . . . . .
(
[0, space.shape[1] // 2 - 1],
[1, space.shape[1] // 2 - 1],
[2, space.shape[1] // 2 - 1],
[2, space.shape[1] // 2],
),
# . . . . . [x][x][x] . . . . .
# . . . . . [x] . . . . . . .
(
[0, space.shape[1] // 2 - 1],
[0, space.shape[1] // 2],
[0, space.shape[1] // 2 + 1],
[1, space.shape[1] // 2 - 1],
),
# . . . . [x][x] . . . . . . .
# . . . . . [x] . . . . . . .
# . . . . . [x] . . . . . . .
(
[0, space.shape[1] // 2 - 1],
[0, space.shape[1] // 2],
[1, space.shape[1] // 2],
[2, space.shape[1] // 2],
),
),
]
return choice(blocks)
def can_transform():
global block_idx, position
block = block_list[block_idx]
next_block = block_list[(block_idx + 1) % len(block_list)]
shift = (position[1][0] - block[1][0], position[1][1] - block[1][1])
next_position = np.zeros_like(position, dtype=np.int64)
for i, p in enumerate(next_block):
next_position[i][0] = p[0] + shift[0]
next_position[i][1] = p[1] + shift[1]
if next_position[:, 0].min() < 0:
next_position[:, 0] -= next_position[:, 0].min()
if next_position[:, 0].max() >= space.shape[0]:
next_position[:, 0] -= next_position[:, 0].max() - space.shape[0] + 1
if next_position[:, 1].min() < 0:
next_position[:, 1] -= next_position[:, 1].min()
if next_position[:, 1].max() >= space.shape[1]:
next_position[:, 1] -= next_position[:, 1].max() - space.shape[1] + 1
for p in next_position:
if space[p[0], p[1]] and list(p) not in position.tolist():
return False
for p in position:
space[p[0], p[1]] = False
position = next_position
block_idx = (block_idx + 1) % len(block_list)
return True
def transform():
if can_transform():
for p in position:
space[p[0], p[1]] = True
show_space()
def get_block():
global position
global block_list
global block_idx
global next_block_list
global next_block_idx
if position is not None:
block_list = next_block_list
block_idx = next_block_idx
else:
block_list = random_choice_block()
block_idx = choice([i for i in range(len(block_list))])
block = block_list[block_idx]
next_block_list = random_choice_block()
next_block_idx = choice([i for i in range(len(next_block_list))])
for p in block:
if space[p[0], p[1]]:
show_space()
return False
for p in block:
position = np.array(
block,
dtype=np.int64,
)
space[p[0], p[1]] = True
show_space()
return True
def can_down():
down_position = deepcopy(position)
down_position[:, 0] += 1
if down_position[:, 0].max() >= space.shape[0]:
return False
for dp in down_position:
if dp.tolist() not in position.tolist() and space[dp[0], dp[1]]:
return False
return True
def can_cancel_layer():
for i, line in enumerate(reversed(space)):
if line.sum() == len(line):
return len(space) - i - 1
return -1
def cancel_layer(layer_label):
global score
space[1 : layer_label + 1] = space[:layer_label]
space[0, :] = False
score += 100
def down():
if can_down():
for p in position:
space[p[0], p[1]] = False
position[:, 0] += 1
for p in position:
space[p[0], p[1]] = True
show_space()
def can_left():
left_position = deepcopy(position)
left_position[:, 1] -= 1
if left_position[:, 1].min() < 0:
return False
for lp in left_position:
if lp.tolist() not in position.tolist() and space[lp[0], lp[1]]:
return False
return True
def left():
if can_left():
for p in position:
space[p[0], p[1]] = False
position[:, 1] -= 1
for p in position:
space[p[0], p[1]] = True
show_space()
def can_right():
right_position = deepcopy(position)
right_position[:, 1] += 1
if right_position[:, 1].max() >= space.shape[1]:
return False
for rp in right_position:
if rp.tolist() not in position.tolist() and space[rp[0], rp[1]]:
return False
return True
def right():
if can_right():
for p in position:
space[p[0], p[1]] = False
position[:, 1] += 1
for p in position:
space[p[0], p[1]] = True
show_space()
def show_space():
print()
print("=" * 10 + "\tNEXT\t" + "=" * 10)
block = np.array(next_block_list[next_block_idx])
block[:, 1] -= block[:, 1].min()
next_block = np.zeros((4, 4), dtype=np.bool8)
for p in block:
next_block[p[0], p[1]] = True
for line in next_block:
s = ""
for item in line:
if item:
s += "[X]"
else:
s += " . "
print(s)
print()
print("=" * 10 + "\tGAME AREA\t" + "=" * 10)
for line in space:
s = ""
for item in line:
if item:
s += "[X]"
else:
s += " . "
print(s)
print("-" * 30 + f"\tSCORE: {score}\t" + "-" * 30)
def keyboard_callback(event: keyboard.KeyboardEvent):
if event.event_type == "down":
if event.name == "left":
left()
elif event.name == "right":
right()
elif event.name == "down":
down()
elif event.name == "up":
transform()
@func_set_timeout(1)
def listen_keyboard():
keyboard.hook(callback=keyboard_callback, suppress=True)
keyboard.wait()
def main():
while get_block():
while can_down():
try:
listen_keyboard()
except FunctionTimedOut:
down()
try:
listen_keyboard()
except FunctionTimedOut:
while can_cancel_layer() > -1:
cancel_layer(can_cancel_layer())
print(f"score: {score}\tgame over !!!")
if __name__ == "__main__":
space_shape = (20, 10)
score = 0
space = np.zeros(shape=space_shape, dtype=np.bool8)
position = None
main()

遇到的问题

  • python 监听字符读入(非 input,input 需要回车结束)好困难,所以该程序 必须用 root 用户下命令行运行…
  • python 的超时阻塞式监听更难,func_timeoutlinux 上运行疑似还有 bug:多线程打开文件但没有关闭,超出 OS limit,在玩十分钟可能才会出现…