[Day2-2] 体验华为方舟编译器:IR测试

上一篇我们讲到完成了方舟编译器的编译过程。目前华为只开源了方舟编译器的IR部分,我们来通过一段简单的代码来测试一下吧。

测试C++代码:

#include <iostream>

using namespace std;

int main() {
    int n;
    cout << "The Answer to Life, the Universe and Everything is:  ";
    cin >> n;
    if (n == 42) {
        cout << "Find highly intelligent lifeform! Start the destruction process!" << endl;
    }
    // 这里为了测试编译器效果
    else if (n != 42) {
        cout << "Yes, you are right!" << endl;
    }
    return 0;
}

文件保存为HelloWorld.cpp。

先用g++测试一下:

g++ HelloUniverse.cpp -o Hello
./Hello

分别测试输入42和10086的情况:

The Answer to Life, the Universe and Everything is:  42
Find highly intelligent lifeform! Start the destruction process!
The Answer to Life, the Universe and Everything is:  42
Find highly intelligent lifeform! Start the destruction process!

看起来是正常运行的。

我们来看一下LLVM下这段代码的IR:

运行

clang -S -emit-llvm HelloUniverse.cpp

输出了ll文件,我们重点看一下cpp代码IR的部分:

; Function Attrs: noinline norecurse optnone uwtable
define dso_local i32 @main() #4 {
  %1 = alloca i32, align 4
  %2 = alloca i32, align 4
  store i32 0, i32* %1, align 4
  %3 = call dereferenceable(272) %"class.std::basic_ostream"* @_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc(%"class.std::basic_ostream"* dereferenceable(272) @_ZSt4cout, i8* getelementptr inbounds ([54 x i8], [54 x i8]* @.str, i32 0, i32 0))
  %4 = call dereferenceable(280) %"class.std::basic_istream"* @_ZNSirsERi(%"class.std::basic_istream"* @_ZSt3cin, i32* dereferenceable(4) %2)
  %5 = load i32, i32* %2, align 4
  %6 = icmp eq i32 %5, 42
  br i1 %6, label %7, label %10

; <label>:7:                                      ; preds = %0
  %8 = call dereferenceable(272) %"class.std::basic_ostream"* @_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc(%"class.std::basic_ostream"* dereferenceable(272) @_ZSt4cout, i8* getelementptr inbounds ([65 x i8], [65 x i8]* @.str.1, i32 0, i32 0))
  %9 = call dereferenceable(272) %"class.std::basic_ostream"* @_ZNSolsEPFRSoS_E(%"class.std::basic_ostream"* %8, %"class.std::basic_ostream"* (%"class.std::basic_ostream"*)* @_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_)
  br label %17

; <label>:10:                                     ; preds = %0
  %11 = load i32, i32* %2, align 4
  %12 = icmp ne i32 %11, 42
  br i1 %12, label %13, label %16

; <label>:13:                                     ; preds = %10
  %14 = call dereferenceable(272) %"class.std::basic_ostream"* @_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc(%"class.std::basic_ostream"* dereferenceable(272) @_ZSt4cout, i8* getelementptr inbounds ([20 x i8], [20 x i8]* @.str.2, i32 0, i32 0))
  %15 = call dereferenceable(272) %"class.std::basic_ostream"* @_ZNSolsEPFRSoS_E(%"class.std::basic_ostream"* %14, %"class.std::basic_ostream"* (%"class.std::basic_ostream"*)* @_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_)
  br label %16

; <label>:16:                                     ; preds = %13, %10
  br label %17

; <label>:17:                                     ; preds = %16, %7
  ret i32 0
}


经过测试,方舟编译器似乎不支持C++,或者是没有开源?我上网查证了一下,少数几篇似乎有意义的文档都在说似乎C++的RT没有放出来(这就很神奇了),所以我们测试一下Java:

以上文件保存为HelloUniverse.java,然后通过OpenJDK编译运行:

javac HelloUniverse.java
java Main

同C++进行测试,输出相同,略过。
然而,使用方舟编译器总是不能成功运行,且输出都是:

Error 40: Init Fail!

有趣的是,如果执行方舟编译器时指定了错误的参数,错误码也是40。

那么今天先到这里辣,我再测试一下!

[Day 2-1]体验华为方舟编译器:开始编译方舟

昨天准备了LLVM,整个编译过程比我想的还要慢,所以就在我睡觉的时候电脑自己跑编译了。

昨天在编译LLVM的时候遇到一个坑,就是在编译到88%的时候,需要编译一个非常复杂的bin(好像是llvm还是clang自身,所以非常大),导致8GB运存和2GB的swap全部吃满,编译失败。遂通过调整swap到32GB(实际上肯定是浪费的,但是毕竟我在睡觉,为了避免过程中再出现问题就设置的很大)。

扯点题外话,随着固态硬盘的发展,实际上从连续读写IO的带宽来看,固态硬盘甚至能达到RAM的低一个数量级,这一事实其实很难令人相信,但是确实是存在的。

目前常见的DDR4带宽在几十GB/s,而最高端NVMe也能够吃满4条PCIe 3.0(当然最近新出的PCIe 4.0暂时不讨论),也就是3500+ MB/s(算上损耗)。这也为swap的性能提供了不少保障。

现在回到话题,我们需要配置llvm,下一步是准备ninja和gn,好在这两者在github上有完整的linux x64使用的release,就不需要自己编译了。让我们开始吧。

配置LLVM

我们需要为方舟编译器指定llvm的路径,因为我不想再把LLVM复制到方舟编译器项目的tools目录下,所以把它指定到我编译的位置。

按照昨天的记录,我们编译后的LLVM应该在~/GitHub/llvm-project/out下,于是在openarkcompiler/build/config/BUILDCONFIG.gn文件中进行配置:

# 为了尽可能避免问题,这里用/home/leon取代~
LLVM_CLANG_ROOT = "/home/leon/GitHub/llvm-project/out"
GN_C_COMPILER = "${LLVM_CLANG_ROOT}/bin/clang"
GN_CXX_COMPILER = "${LLVM_CLANG_ROOT}/bin/clang++"
GN_AR_COMPILER = "${LLVM_CLANG_ROOT}/bin/llvm-ar"

配置Ninja

Ninja的配置比较简单,在Ninja的GitHub官方项目页面:https://github.com/ninja-build/ninja 下载Release里面最新的Linux binary即可(注:Ninja页面截止发稿当日,最新版为1.9.0,这也是方舟编译器的文档所要求的版本)。

然后,将Ninja放入方舟编译器的tools目录下。按照文档的说法,ninja放在了tools/ninja-1.9.0/ninja,但是由于我没有把LLVM一起放进来,实际上tools目录没有这么复杂,所以放在tools/ninja了。

再编辑Makefile,在里面指定Ninja的位置:

NINJA := ${MAPLE_ROOT}/tools/ninja

配置gn

gn的配置和ninja大同小异。

gn在google的gn官网下载binary:https://gn.googlesource.com/gn/

然后,放在tools目录下(同Ninja),在Makefile配置gn的位置:

GN := ${MAPLE_ROOT}/tools/gn

开始编译方舟编译器

现在所有的环境配置工作都完成了,可以开始编译方舟编译器了。进入项目根目录,执行如下命令:

. build/envsetup.sh
make

这样就能编译好方舟编译器的Release版本了。当然,需要Debug版本,可以在make时指定BUILD_TYPE=DEBUG,这里选择RELEASE版。

稍等几分钟,方舟编译器就编译完成了。

编译完成后,在项目根目录的out文件夹内能够看到编译完成后的文件,bin下有一个maple可执行bin,应该就是我们要研究的编译器本体了!

进入out/bin文件夹,执行

./maple --help

输出:

USAGE: maple [options]

 Options:
  -h --help [command]             Print usage and exit.

  --version [command]             Print version and exit.

  --infile file1,file2,file3      Input files.

  --mplt=file1,file2,file3        Import mplt files.

  -O0                             No optimization.

  --save-temps                    Do not delete intermediate files.
                                  --save-temps Save all intermediate files.
                                  --save-temps=file1,file2,file3 Save the
                                  target files.

  --run=cmd1:cmd2                 The name of executables that are going
                                  to execute. IN SEQUENCE.
                                  Separated by ":".Available exe names:
                                  jbc2mpl, me, mpl2mpl, mplcg
                                  Input file must match the tool can
                                  handle

  --option="opt1:opt2"          Options for each executable,
                                  separated by ":".
                                  The sequence must match the sequence in
                                  --run.

  -time-phases                    Timing phases and print percentages

  --genmempl                      Generate me.mpl file

  --genVtableImpl                 Generate VtableImpl.mpl file

  --debug                         Print debug info.

  -h-me --help-me                 Print usage and exit.Available command names:
                                  me

  -h-mpl2mpl --help-mpl2mpl       Print usage and exit.Available command names:
                                  mpl2mpl

[Day 1] 体验华为方舟编译器:准备llvm和clang

大家好,今天华为开放了方舟编译器的部分源代码,据查应该是MapleIR部分。得到消息后我立刻开始了方舟编译器的配置与试用。由于电脑配置有限,配置过程冗长,所以打算分日记录我的体验过程。今天是第一天:准备llvm和clang。

环境:
我在MacBook Pro 15 inch 2017上安装了Parallels Desktop(购买于春节期间的企业版,699元一年的订阅版,好贵)。配置如下:

  • CPU:4 * vCPU (Intel i7-7820HQ@2.9GHz)
  • RAM:8GB
  • 存储:在外置WD MyPassport 512G移动固态硬盘上创建SATA虚拟机硬盘。
  • 系统:Ubuntu Desktop 19.04,linux kernel 5.0 

Part I. 准备llvm和clang

方舟编译器的文档(在源码的doc下的Development_Preparation.md)的说明中,要求先准备好llvm+clang。为了尽可能避免问题,选择进行源码编译。

先去llvm官网:https://clang.llvm.org/get_started.html查看llvm+clang的源码编译教程:

  1. Get the required tools.
    • See Getting Started with the LLVM System – Requirements.
    • Note also that Python is needed for running the test suite. Get it at: http://www.python.org/download
    • Standard build process uses CMake. Get it at: http://www.cmake.org/download
  2. Check out the LLVM project:
    • Change directory to where you want the llvm directory placed.
    • git clone https://github.com/llvm/llvm-project.git
  3. Build LLVM and Clang:
    • cd llvm-project
    • mkdir build (in-tree build is not supported)
    • cd build
    • cmake -DLLVM_ENABLE_PROJECTS=clang -G “Unix Makefiles” ../llvm
    • make
    • This builds both LLVM and Clang for debug mode.
    • Note: For subsequent Clang development, you can just run make clang.

第一步,准备好编译需要的工具,例如cmake,这个很简单。

$ sudo apt install cmake

其他的依赖安装都很简单,这里不再赘述。

第二步,clone llvm项目

$ git clone https://github.com/llvm/llvm-project.git

我的项目文件放在~/GitHub/llvm-project下,后面均假设在这个目录下操作。

第三步,编译

需要注意的是,LLVM不允许在项目tree下进行编译,需要在项目文件夹下创建一个新的文件夹,然后在其中进行编译。

# 创建一个叫做out的文件夹
$ mkdir out
# 进入out文件夹
$ cd out
# 执行CMake
$ cmake -DLLVM_ENABLE_PROJECTS=clang -G "Unix Makefiles" ../llvm

然后稍等CMake进行配置。注意执行CMake的时候指定-DLLVM_ENABLE_PROJECTS=clang来确保LLVM和Clang都进行编译,不然会导致编译后只能找到llvm找不到clang(别问我为什么知道……)

然后,开始编译:

$ make

这一步非常非常非常慢(至少在我的虚拟机上需要大约两个小时以上),如图

然后llvm和clang就成功编译啦!(截止发文,仍然没有编译完,所以剩下的内容会慢慢向大家展示!)

【手势识别学习笔记3】修正存储bug,以及录制需要的数据集

回顾

上一期我们留了一个小问题:设计好的录制工具并不能保存图片,每次调用cv2.imwrite()的时候返回值总是False,今天就来解决这个问题。

为什么会这样?

翻阅cv2.imwrite的官方文档,看到了对这个函数的限制:

Python: cv2.imwrite(filename, img[, params]) → retval

它的Parameters:

  • filename – Name of the file.
  • image – Image to be saved.
  • params

    Format-specific save parameters encoded as pairs paramId_1, paramValue_1, paramId_2, paramValue_2, ... . The following parameters are currently supported:

    • For JPEG, it can be a quality ( CV_IMWRITE_JPEG_QUALITY ) from 0 to 100 (the higher is the better). Default value is 95.
    • For PNG, it can be the compression level ( CV_IMWRITE_PNG_COMPRESSION ) from 0 to 9. A higher value means a smaller size and longer compression time. Default value is 3.
    • For PPM, PGM, or PBM, it can be a binary format flag ( CV_IMWRITE_PXM_BINARY ), 0 or 1. Default value is 1.

另外,文档中还说到,如果要保存JPG\PNG\TIFF格式的文档,需要是8或无符号16位图:

The function imwrite saves the image to the specified file. The image format is chosen based on the filename extension (see imread() for the list of extensions). Only 8-bit (or 16-bit unsigned (CV_16U) in case of PNG, JPEG 2000, and TIFF) single-channel or 3-channel (with ‘BGR’ channel order) images can be saved using this function. If the format, depth or channel order is different, use Mat::convertTo() , and cvtColor() to convert it before saving. Or, use the universal FileStorage I/O functions to save the image to XML or YAML format.

It is possible to store PNG images with an alpha channel using this function. To do this, create 8-bit (or 16-bit) 4-channel image BGRA, where the alpha channel goes last. Fully transparent pixels should have alpha set to 0, fully opaque pixels should have alpha set to 255/65535. The sample below shows how to create such a BGRA image and store to PNG file. It also demonstrates how to set custom compression parameters.

所以是图片格式的问题咯?

既然之前执行过cvtColor()把图片换到灰度空间上,那是不是不能保存就是格式的问题呢?

查阅cvtColor()支持的格式

回顾上次调用cvtColor()的时候,第二个参数指定的是cv2.COLOR_RGB2GRAY,我们或许可以看下cv2支持哪些格式。

在命令行下进入含有cv2的python环境,执行:

>>> import cv2
>>> print ([x for x in dir(cv2) if x.startswith('COLOR_')])

得到一大堆结果:

['COLOR_BAYER_BG2BGR', 'COLOR_BAYER_BG2BGRA', 'COLOR_BAYER_BG2BGR_EA', 'COLOR_BAYER_BG2BGR_VNG', 'COLOR_BAYER_BG2GRAY', 'COLOR_BAYER_BG2RGB', 'COLOR_BAYER_BG2RGBA', 'COLOR_BAYER_BG2RGB_EA', 'COLOR_BAYER_BG2RGB_VNG', 'COLOR_BAYER_GB2BGR', 'COLOR_BAYER_GB2BGRA', 'COLOR_BAYER_GB2BGR_EA', 'COLOR_BAYER_GB2BGR_VNG', 'COLOR_BAYER_GB2GRAY', 'COLOR_BAYER_GB2RGB', 'COLOR_BAYER_GB2RGBA', 'COLOR_BAYER_GB2RGB_EA', 'COLOR_BAYER_GB2RGB_VNG', 'COLOR_BAYER_GR2BGR', 'COLOR_BAYER_GR2BGRA', 'COLOR_BAYER_GR2BGR_EA', 'COLOR_BAYER_GR2BGR_VNG', 'COLOR_BAYER_GR2GRAY', 'COLOR_BAYER_GR2RGB', 'COLOR_BAYER_GR2RGBA', 'COLOR_BAYER_GR2RGB_EA', 'COLOR_BAYER_GR2RGB_VNG', 'COLOR_BAYER_RG2BGR', 'COLOR_BAYER_RG2BGRA', 'COLOR_BAYER_RG2BGR_EA', 'COLOR_BAYER_RG2BGR_VNG', 'COLOR_BAYER_RG2GRAY', 'COLOR_BAYER_RG2RGB', 'COLOR_BAYER_RG2RGBA', 'COLOR_BAYER_RG2RGB_EA', 'COLOR_BAYER_RG2RGB_VNG', 'COLOR_BGR2BGR555', 'COLOR_BGR2BGR565', 'COLOR_BGR2BGRA', 'COLOR_BGR2GRAY', 'COLOR_BGR2HLS', 'COLOR_BGR2HLS_FULL', 'COLOR_BGR2HSV', 'COLOR_BGR2HSV_FULL', 'COLOR_BGR2LAB', 'COLOR_BGR2LUV', 'COLOR_BGR2Lab', 'COLOR_BGR2Luv', 'COLOR_BGR2RGB', 'COLOR_BGR2RGBA', 'COLOR_BGR2XYZ', 'COLOR_BGR2YCR_CB', 'COLOR_BGR2YCrCb', 'COLOR_BGR2YUV', 'COLOR_BGR2YUV_I420', 'COLOR_BGR2YUV_IYUV', 'COLOR_BGR2YUV_YV12', 'COLOR_BGR5552BGR', 'COLOR_BGR5552BGRA', 'COLOR_BGR5552GRAY', 'COLOR_BGR5552RGB', 'COLOR_BGR5552RGBA', 'COLOR_BGR5652BGR', 'COLOR_BGR5652BGRA', 'COLOR_BGR5652GRAY', 'COLOR_BGR5652RGB', 'COLOR_BGR5652RGBA', 'COLOR_BGRA2BGR', 'COLOR_BGRA2BGR555', 'COLOR_BGRA2BGR565', 'COLOR_BGRA2GRAY', 'COLOR_BGRA2RGB', 'COLOR_BGRA2RGBA', 'COLOR_BGRA2YUV_I420', 'COLOR_BGRA2YUV_IYUV', 'COLOR_BGRA2YUV_YV12', 'COLOR_BayerBG2BGR', 'COLOR_BayerBG2BGRA', 'COLOR_BayerBG2BGR_EA', 'COLOR_BayerBG2BGR_VNG', 'COLOR_BayerBG2GRAY', 'COLOR_BayerBG2RGB', 'COLOR_BayerBG2RGBA', 'COLOR_BayerBG2RGB_EA', 'COLOR_BayerBG2RGB_VNG', 'COLOR_BayerGB2BGR', 'COLOR_BayerGB2BGRA', 'COLOR_BayerGB2BGR_EA', 'COLOR_BayerGB2BGR_VNG', 'COLOR_BayerGB2GRAY', 'COLOR_BayerGB2RGB', 'COLOR_BayerGB2RGBA', 'COLOR_BayerGB2RGB_EA', 'COLOR_BayerGB2RGB_VNG', 'COLOR_BayerGR2BGR', 'COLOR_BayerGR2BGRA', 'COLOR_BayerGR2BGR_EA', 'COLOR_BayerGR2BGR_VNG', 'COLOR_BayerGR2GRAY', 'COLOR_BayerGR2RGB', 'COLOR_BayerGR2RGBA', 'COLOR_BayerGR2RGB_EA', 'COLOR_BayerGR2RGB_VNG', 'COLOR_BayerRG2BGR', 'COLOR_BayerRG2BGRA', 'COLOR_BayerRG2BGR_EA', 'COLOR_BayerRG2BGR_VNG', 'COLOR_BayerRG2GRAY', 'COLOR_BayerRG2RGB', 'COLOR_BayerRG2RGBA', 'COLOR_BayerRG2RGB_EA', 'COLOR_BayerRG2RGB_VNG', 'COLOR_COLORCVT_MAX', 'COLOR_GRAY2BGR', 'COLOR_GRAY2BGR555', 'COLOR_GRAY2BGR565', 'COLOR_GRAY2BGRA', 'COLOR_GRAY2RGB', 'COLOR_GRAY2RGBA', 'COLOR_HLS2BGR', 'COLOR_HLS2BGR_FULL', 'COLOR_HLS2RGB', 'COLOR_HLS2RGB_FULL', 'COLOR_HSV2BGR', 'COLOR_HSV2BGR_FULL', 'COLOR_HSV2RGB', 'COLOR_HSV2RGB_FULL', 'COLOR_LAB2BGR', 'COLOR_LAB2LBGR', 'COLOR_LAB2LRGB', 'COLOR_LAB2RGB', 'COLOR_LBGR2LAB', 'COLOR_LBGR2LUV', 'COLOR_LBGR2Lab', 'COLOR_LBGR2Luv', 'COLOR_LRGB2LAB', 'COLOR_LRGB2LUV', 'COLOR_LRGB2Lab', 'COLOR_LRGB2Luv', 'COLOR_LUV2BGR', 'COLOR_LUV2LBGR', 'COLOR_LUV2LRGB', 'COLOR_LUV2RGB', 'COLOR_Lab2BGR', 'COLOR_Lab2LBGR', 'COLOR_Lab2LRGB', 'COLOR_Lab2RGB', 'COLOR_Luv2BGR', 'COLOR_Luv2LBGR', 'COLOR_Luv2LRGB', 'COLOR_Luv2RGB', 'COLOR_M_RGBA2RGBA', 'COLOR_RGB2BGR', 'COLOR_RGB2BGR555', 'COLOR_RGB2BGR565', 'COLOR_RGB2BGRA', 'COLOR_RGB2GRAY', 'COLOR_RGB2HLS', 'COLOR_RGB2HLS_FULL', 'COLOR_RGB2HSV', 'COLOR_RGB2HSV_FULL', 'COLOR_RGB2LAB', 'COLOR_RGB2LUV', 'COLOR_RGB2Lab', 'COLOR_RGB2Luv', 'COLOR_RGB2RGBA', 'COLOR_RGB2XYZ', 'COLOR_RGB2YCR_CB', 'COLOR_RGB2YCrCb', 'COLOR_RGB2YUV', 'COLOR_RGB2YUV_I420', 'COLOR_RGB2YUV_IYUV', 'COLOR_RGB2YUV_YV12', 'COLOR_RGBA2BGR', 'COLOR_RGBA2BGR555', 'COLOR_RGBA2BGR565', 'COLOR_RGBA2BGRA', 'COLOR_RGBA2GRAY', 'COLOR_RGBA2M_RGBA', 'COLOR_RGBA2RGB', 'COLOR_RGBA2YUV_I420', 'COLOR_RGBA2YUV_IYUV', 'COLOR_RGBA2YUV_YV12', 'COLOR_RGBA2mRGBA', 'COLOR_XYZ2BGR', 'COLOR_XYZ2RGB', 'COLOR_YCR_CB2BGR', 'COLOR_YCR_CB2RGB', 'COLOR_YCrCb2BGR', 'COLOR_YCrCb2RGB', 'COLOR_YUV2BGR', 'COLOR_YUV2BGRA_I420', 'COLOR_YUV2BGRA_IYUV', 'COLOR_YUV2BGRA_NV12', 'COLOR_YUV2BGRA_NV21', 'COLOR_YUV2BGRA_UYNV', 'COLOR_YUV2BGRA_UYVY', 'COLOR_YUV2BGRA_Y422', 'COLOR_YUV2BGRA_YUNV', 'COLOR_YUV2BGRA_YUY2', 'COLOR_YUV2BGRA_YUYV', 'COLOR_YUV2BGRA_YV12', 'COLOR_YUV2BGRA_YVYU', 'COLOR_YUV2BGR_I420', 'COLOR_YUV2BGR_IYUV', 'COLOR_YUV2BGR_NV12', 'COLOR_YUV2BGR_NV21', 'COLOR_YUV2BGR_UYNV', 'COLOR_YUV2BGR_UYVY', 'COLOR_YUV2BGR_Y422', 'COLOR_YUV2BGR_YUNV', 'COLOR_YUV2BGR_YUY2', 'COLOR_YUV2BGR_YUYV', 'COLOR_YUV2BGR_YV12', 'COLOR_YUV2BGR_YVYU', 'COLOR_YUV2GRAY_420', 'COLOR_YUV2GRAY_I420', 'COLOR_YUV2GRAY_IYUV', 'COLOR_YUV2GRAY_NV12', 'COLOR_YUV2GRAY_NV21', 'COLOR_YUV2GRAY_UYNV', 'COLOR_YUV2GRAY_UYVY', 'COLOR_YUV2GRAY_Y422', 'COLOR_YUV2GRAY_YUNV', 'COLOR_YUV2GRAY_YUY2', 'COLOR_YUV2GRAY_YUYV', 'COLOR_YUV2GRAY_YV12', 'COLOR_YUV2GRAY_YVYU', 'COLOR_YUV2RGB', 'COLOR_YUV2RGBA_I420', 'COLOR_YUV2RGBA_IYUV', 'COLOR_YUV2RGBA_NV12', 'COLOR_YUV2RGBA_NV21', 'COLOR_YUV2RGBA_UYNV', 'COLOR_YUV2RGBA_UYVY', 'COLOR_YUV2RGBA_Y422', 'COLOR_YUV2RGBA_YUNV', 'COLOR_YUV2RGBA_YUY2', 'COLOR_YUV2RGBA_YUYV', 'COLOR_YUV2RGBA_YV12', 'COLOR_YUV2RGBA_YVYU', 'COLOR_YUV2RGB_I420', 'COLOR_YUV2RGB_IYUV', 'COLOR_YUV2RGB_NV12', 'COLOR_YUV2RGB_NV21', 'COLOR_YUV2RGB_UYNV', 'COLOR_YUV2RGB_UYVY', 'COLOR_YUV2RGB_Y422', 'COLOR_YUV2RGB_YUNV', 'COLOR_YUV2RGB_YUY2', 'COLOR_YUV2RGB_YUYV', 'COLOR_YUV2RGB_YV12', 'COLOR_YUV2RGB_YVYU', 'COLOR_YUV420P2BGR', 'COLOR_YUV420P2BGRA', 'COLOR_YUV420P2GRAY', 'COLOR_YUV420P2RGB', 'COLOR_YUV420P2RGBA', 'COLOR_YUV420SP2BGR', 'COLOR_YUV420SP2BGRA', 'COLOR_YUV420SP2GRAY', 'COLOR_YUV420SP2RGB', 'COLOR_YUV420SP2RGBA', 'COLOR_YUV420p2BGR', 'COLOR_YUV420p2BGRA', 'COLOR_YUV420p2GRAY', 'COLOR_YUV420p2RGB', 'COLOR_YUV420p2RGBA', 'COLOR_YUV420sp2BGR', 'COLOR_YUV420sp2BGRA', 'COLOR_YUV420sp2GRAY', 'COLOR_YUV420sp2RGB', 'COLOR_YUV420sp2RGBA', 'COLOR_mRGBA2RGBA']

相当壮观啊!

所以只要把GRAY空间上的图片换回RGB就行了吗?

才怪嘞(逃

真·解决不能保存的bug

后来发现,原来不能保存的原因仅仅是桌面上没有目标文件夹……(囧)

修改保存图片的部分:

@staticmethod
def save_image(self, image, name):
    if self.__save_count is 100:
        self.__save_count = 0
        self.__is_now_saving = False
        print('Done!')
        return
    
    else:
        print('Saving ' + name + ' for #' + str(self.__save_count))
        pic_name = name + '_' + str(self.__save_count)
        folderpath = 'C:\\Users\\LeonWong\\Desktop\\' + name
        # 在这里创建文件夹
        os.makedirs(folderpath, 0o777, True)
        path = folderpath + '\\' + pic_name + '.jpg'
        print('Path: ' + path)
        flag = cv2.imwrite(path, image)
        print('Saved? ' + str(flag))
        self.__save_count += 1
        time.sleep(0.1)

再试一下,保存成功啦。

录制需要的手势

现在小公举工具已经完成了,下一步就是要用它来创建需要的数据集。

再一次提醒,请先手动修改代码中的保存路径

打开小工具,切换到二值化模式(按2):

感谢老李头友情出镜,肖像已授权,原创图片转载请先联系本公众号(或https://leonwong.cn)。

然后按3开始新的录制,小工具会录制100张手势照片,放在桌面上。

打开一个666的手势,可以看到二值化后的录制结果,图片大小为200 * 200:

录制几组手势后就可以开始训练了。

预告!

下一篇就要开始使用TensorFlow进行神经网络的训练了,初步计划采用CNN,对200*200的手势图片进行四次卷积(当然计划有可能会变动),预计本周末开始做,敬请期待!

【手势识别学习笔记2】利用摄像头获取数据集(下)

上一篇我们讲到了使用OpenCV框架帮助我们调用摄像头进行拍照,今天我们就要开始利用这个方法创建一个小工具,帮助我们产生需要的数据集。

小工具界面设计

在上一篇的讲解中,我们知道OpenCV有一个非常好用的函数:

cv2.waitKey()

通过这个函数,我们能够等待用户从键盘输入数据,那么很自然地,我们可以创建一个实时预览的窗口,并通过提供按键切换功能的方式,为我们创建一个简单的数据集录制工具。

另外,为了方便地把这个工具放进最终的工具包里,我们可以把它封装进class。

选项

主界面的设计比较简单,只需要显示实时的视频(请回顾上一期)和几个必要的选项。

  • 1:RGB模式
  • 2:Binary模式
  • 3:录制手势
  • 0:退出

大致来说这几个就够了。

那么这几个文字放在哪里呢?

cv2为我们提供了

cv2.putText()

函数,函数的作用是在拍摄的frame上显示文字。

为了方便可以把这一部分抽出来做一个函数:

@staticmethod
def show_capturer_options(frame):
 font_face = cv2.FONT_HERSHEY_COMPLEX
 font_size = 0.5
 font_color = (0, 255, 0)
 cv2.putText(frame, 'Options:', (18, 18), font_face, font_size, font_color)
 cv2.putText(frame, '1: RGB Mode', (18, 18 + 18), font_face, font_size, font_color)
 cv2.putText(frame, '2: Binary Mode', (18, 18 + 2 * 18), font_face, font_size, font_color)
 cv2.putText(frame, '3: New Gesture', (18, 18 + 3 * 18), font_face, font_size, font_color)
 cv2.putText(frame, '0: Quit', (18, 18 + 4 * 18), font_face, font_size, font_color)

解释一下代码:

font_face是cv2提供的字体选项,cv2.FONT_HERSHEY_SIMPLEX是正常大小无衬线字体。

font_size是字体缩放

font_color是RGB绿色。

然后依次按顺序把选项文字写在frame上即可。

显示主界面

利用上一篇中讲到的

cv2.imshow()

就可以显示主界面了,然后利用循环就能实时显示录制的内容了。

主循环代码如下:

def mainloop(self):
    while True:
        flag, frame = self.__cap.read()
        frame = cv2.flip(frame, 2)
        
        GestureCapturer.show_capturer_options(frame)
        key = cv2.waitKey(1) & 0xFF
        
        if key is ord('0'):
            self.quit_capturer()
            return
        
        cv2.imshow('Video Capturer', frame)

另外在退出的时候需要关闭窗口,所以再写一个函数用于退出

def quit_capturer(self):
    self.__cap.release()
    cv2.destroyAllWindows()

那么拍摄器的界面就完成了。

看一下全部的代码吧:

class GestureCapturer(object):
    
    def __init__(self):
        self.__cap = cv2.VideoCapture(0)
        
    @staticmethod
    def show_capturer_options(frame):
        font_face = cv2.FONT_HERSHEY_COMPLEX
        font_size = 0.5
        font_color = (0, 255, 0)
        cv2.putText(frame, 'Options:', (18, 18), font_face, font_size, font_color)
        cv2.putText(frame, '1: RGB Mode', (18, 18 + 18), font_face, font_size, font_color)
        cv2.putText(frame, '2: Binary Mode', (18, 18 + 2 * 18), font_face, font_size, font_color)
        cv2.putText(frame, '3: New Gesture', (18, 18 + 3 * 18), font_face, font_size, font_color)
        cv2.putText(frame, '0: Quit', (18, 18 + 4 * 18), font_face, font_size, font_color)
        
    def mainloop(self):
        while True:
            flag, frame = self.__cap.read()
            frame = cv2.flip(frame, 2)
        
            GestureCapturer.show_capturer_options(frame)
            key = cv2.waitKey(1) & 0xFF
        
            if key is ord('0'):
                self.quit_capturer()
                return
        
            cv2.imshow('Video Capturer', frame)
            
    def quit_capturer(self):
        self.__cap.release()
        cv2.destroyAllWindows()

效果也很不错:

需要注意的是,这个界面必须按0退出,点击右上角是不行的。(原因就在mainloop里面,欢迎各位自行修改代码)。

加入录制区域和录制功能

现在我们的主界面已经完成了,但是还只能显示摄像头捕获的画面,我们要为它加上拍摄功能。

在主界面框出录制区域

没有必要录制整个摄像头的画面,主要在于:

    1. 整个摄像头的画面太大了,实际上在使用摄像头的过程中,手出现的位置并不是很大,而且保存很大的图片会提高后期训练的难度。(电脑吃不消啊
    2. 摄像头的分辨率并不统一,这会影响代码的可移植性。

因此要对录制画面进行裁剪。

首先我们要在录制的画面上框出录制区域,cv2提供了一个

cv2.rectangle()

函数,能直接帮助我们画出一个正方形。

GestureCapturer里面添加一个名为draw_saving_area的函数:

@staticmethod
def draw_saving_area(frame):
    cv2.rectangle(frame, (100, 100), (300, 300), (0, 255, 0))

并且在mainloop中调用它(只要在imshow之前进行处理即可)。

现在我们就画出了从(100, 100)到(300, 300)的线段为对角线的正方形,以后就以这个位置作为录制区域了。

处理录制的图像

现在我们框出了录制区域,为了方便,可以把录制区域单独拿出来显示在一个小窗口里,同时,添加显示RGB模式和Binary模式切换的功能。

继续添加函数:

@staticmethod
def binary_mask(frame):
    area = frame[100:300, 100:300]
    gray = cv2.cvtColor(area, cv2.COLOR_RGB2GRAY)
    blur = cv2.GaussianBlur(gray, (5, 5), 2)
    at = cv2.adaptiveThreshold(blur, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 11, 2)
    flag, bin = cv2.threshold(at, 70, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
    return bin

这段代码可以说相当复杂,我们一条一条来看:

area = frame[100:300, 100:300]

是把我们要录制的区域扣了出来,这一步比较好理解(也就这一步比较好理解……)

gray = cv2.cvtColor(area, cv2.COLOR_RGB2GRAY)

cvtColor提供了一个颜色空间转换的API,指定第二个参数为COLOR_RGB2GRAY,很显然是将RGB空间上的色彩转换为灰度。

但是直接使用这样的转换是有问题的,所以引入了下面的处理方法:

高斯模糊

先说低通滤波器。低通滤波器主要的作用是降低图片的变化率,这样就能平滑并替代那些强度变化明显的区域。低通滤波器的算法可以是将每个像素替换为周围像素的均值。

但是有些时候,需要对一个像素的周围的像素给予更多的重视,这时候就需要重新分配权重,例如使用高斯函数来分配权重。

高斯模糊实际上也是一种低通滤波器。

blur = cv2.GaussianBlur(gray, (5, 5), 2)

其中,参数(5, 5)是高斯模糊矩阵(半径等参数),2是标准差,二者越大,图片的模糊程度越高。

自适应阀值二值化和固定阀值二值化

二值化操作是对一个灰度图片进行二值化处理,亦即转化为黑白两色。

固定阀值二值化是对单个像素超过阀值后进行的处理。

自适应阈值二值化函数根据图片一小块区域的值来计算对应区域的阈值,从而得到新的图片。

先简介一下固定阀值二值化:

固定阀值二值化处理有多种操作类型,cv2提供了:

  • cv2.THRESH_BINARY
  • cv2.THRESH_BINARY_INV
  • cv2.THRESH_TRUNC
  • cv2.THRESH_TOZERO
  • cv2.THRESH_TOZERO_INV

对应的具体效果如下图:

图片非原创,侵删

而自适应阀值二值化提供的方法更为可靠,它的参数是:

cv2.adaptiveThreshold(src, maxval, thresh_type, type, Block Size, C)
  • src: 输入图,只能输入单通道图像,通常来说为灰度图。
  • maxval: 当像素值超过了阈值(或者小于阈值,根据type来决定),所赋予的值。
  • thresh_type: 阈值的计算方法,包含以下2种类型:cv2.ADAPTIVE_THRESH_MEAN_C; cv2.ADAPTIVE_THRESH_GAUSSIAN_C.
  • type:二值化操作的类型,与固定阈值函数相同,包含以下5种类型: cv2.THRESH_BINARY; cv2.THRESH_BINARY_INV; cv2.THRESH_TRUNC; cv2.THRESH_TOZERO;cv2.THRESH_TOZERO_INV.
  • Block Size: 图片中分块的大小
  • C :阈值计算方法中的常数项

我们的代码使用了:

at = cv2.adaptiveThreshold(blur, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 11, 2)

进行了自适应阀值二值化。

OTSU:基于直方图的二值化

这又是一种二值化方法,简单来看,OTSU进行了这样的过程:

  1. 计算图像直方图
  2. 通过设定的阀值,将图像的像素分为两组。
  3. 分别计算两组内的偏移数,并把偏移数相加。
  4. 重复1-3的步骤,直到得到最小偏移数,其所对应的值即为结果阈值。

有关OTSU的介绍很多,这里就不在赘述了。

flag, bin = cv2.threshold(at, 70, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)

最后就得到了二值化后的图像。

完成!

现在我们有了录制窗口,也有了二值化功能,还需要做的事情有:

  1. 单独显示截取的图片
  2. 命名和保存截取的图片

这两个都比较简单。

单独显示截取的区域

很简单,直接上代码:

def mainloop(self):
    is_binary_mode = False
    while True:
        ret, frame = self.__cap.read()
        frame = cv2.flip(frame, 2)
        
        GestureCapturer.show_capturer_options(frame)
        key = cv2.waitKey(1) & 0xFF
        
        if key is ord('0'):
            self.quit_capturer() 
            return
        
        if key is ord('1'):
            is_binary_mode = False
            
        if key is ord('2'):
            is_binary_mode = True
            
        GestureCapturer.draw_saving_area(frame)
        
        if is_binary_mode:
            binary_area = GestureCapturer.binary_mask(frame)
            cv2.imshow('Captured Area', binary_area)
            
        else:
            cv2.imshow('Captured Area', frame[100:300, 100:300])
            
        cv2.imshow('Video Capturer', frame)

设置保存功能

保存功能是当用户按下‘3’键的时候,提示用户选择一个名称,然后保存100帧。

保存功能使用

cv2.imwrite()

即可。(目前在我这里有些bug,保存的部分会另外介绍)

最后贴一下今天全部的代码吧

import cv2
import time
import os


class GestureCapturer(object):

    def __init__(self):
        self.__cap = cv2.VideoCapture(0)
        self.__is_now_saving = False
        self.__save_count = 0
        self.__name = 'Default Name'

    @staticmethod
    def show_capturer_options(frame):
        font_face = cv2.FONT_HERSHEY_SIMPLEX
        font_size = 0.5
        font_color = (0, 255, 0)
        cv2.putText(frame, 'Options:', (18, 18), font_face, font_size, font_color)
        cv2.putText(frame, '1: RGB Mode', (18, 18 + 18), font_face, font_size, font_color)
        cv2.putText(frame, '2: Binary Mode', (18, 18 + 2 * 18), font_face, font_size, font_color)
        cv2.putText(frame, '3: New Gesture', (18, 18 + 3 * 18), font_face, font_size, font_color)
        cv2.putText(frame, '0: Quit', (18, 18 + 4 * 18), font_face, font_size, font_color)

    def mainloop(self):
        is_binary_mode = False
        self.__is_now_saving = False
        while True:
            ret, frame = self.__cap.read()
            frame = cv2.flip(frame, 2)

            GestureCapturer.show_capturer_options(frame)
            key = cv2.waitKey(1) & 0xFF

            if key is ord('0'):
                self.quit_capturer()
                return

            if key is ord('1'):
                is_binary_mode = False

            if key is ord('2'):
                is_binary_mode = True

            if key is ord('3'):
                self.__is_now_saving = True
                self.__name = input('Input gesture name:')
                self.__save_count = 0

            GestureCapturer.draw_saving_area(frame)

            if is_binary_mode:
                binary_area = GestureCapturer.binary_mask(frame)
                cv2.imshow('Captured Area', binary_area)
                if self.__is_now_saving:
                    self.save_image(self, binary_area, self.__name)

            else:
                cv2.imshow('Captured Area', frame[100:300, 100:300])
                if self.__is_now_saving:
                    self.save_image(self, frame[100:300, 100:300], self.__name)

            cv2.imshow('Video Capturer', frame)

    def quit_capturer(self):
        self.__cap.release()
        cv2.destroyAllWindows()

    @staticmethod
    def draw_saving_area(frame):
        cv2.rectangle(frame, (100, 100), (300, 300), (0, 255, 0))

    @staticmethod
    def binary_mask(frame):
        area = frame[100:300, 100:300]
        gray = cv2.cvtColor(area, cv2.COLOR_RGB2GRAY)
        blur = cv2.GaussianBlur(gray, (5, 5), 2)
        at = cv2.adaptiveThreshold(blur, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 11, 2)
        flag, bin = cv2.threshold(at, 70, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
        return bin

    @staticmethod
    def save_image(self, image, name):
        if (self.__save_count is 100):
            self.__save_count = 0
            self.__is_now_saving = False
            print('Done!')
            return

        else:
            print('Saving ' + name + ' for #' + str(self.__save_count))
            pic_name = name + str(self.__save_count)
            # 这里的路径需要注意
            path = 'C:\\Users\\LeonWong\\Desktop\\' + name + '\\' + pic_name + '.jpg'
            print('Path: ' + path)
            flag = cv2.imwrite(path, image)
            print('Saved? ' + str(flag))
            self.__save_count += 1
            time.sleep(0.1)


vc = GestureCapturer()
vc.mainloop()

【实时手势识别学习笔记1】使用OpenCV调用摄像头获取数据集(上)

今天是2008年5月12日汶川大地震10周年,逝者安息。

 

在完成了环境搭建(具体请翻阅实时手势识别学习笔记第0篇)后,就可以着手搭建学习框架咯。不过由于我们组决定实现的是手语教学,所以需要让机器学习很多的我们自定义的手势,显然这个很难通过从网络获得的数据实现,因此我们有必要通过自己的摄像头来获取手势。同时,因为要拍摄用户的手势,同样要用到摄像头,总之,我们选择了OpenCV框架来帮我们实现这个功能。

关于cv2.VideoCapture

VideoCapture是一个类,构造时需要传递一个Object参数,作为视频的来源。在这里使用0作为参数,则VideoCapture从默认摄像头获取图像。

cap = cv2.VideoCapture(0)

然后可以通过摄像头的指示灯判断摄像头是否开启(逃

其实是可以通过:

cap.isOpened()

来判断,在命令行下如果输出了True,就可以判断摄像头已经开启了。

最后记得使用

cap.release()

释放摄像头。

使用VideoCapture拍摄一张照片

打开命令行(如果是根据第0篇搭建的环境,请进入Anaconda Prompt),然后进入python环境,并执行:

import cv2
cap = cv2.VideoCapture(0)

然后执行

flag, frame = cap.read()

cap.read()提供给我们两个返回值,第一个记为flag,第二个记为frame。

flag保存了这一次read有没有成功读到一帧的信息,是一个布尔值,frame则是截取的帧。

在命令行下查看它们:

>>> flag
>>> frame

可以看到flag为True表示截取成功,frame是一个大矩阵,看起来是一张图片的样子。

然后保存这张照片:

cv2.imwrite("C:\\Users\\LeonWong\\Desktop\\a.jpg", frame)

可以看到照片被保存在桌面上了(为了不让我英俊帅气的脸露在网上,照片进行了处理)<-噫

用窗口显示实时视频

现在我们实现了拍照,那么照片连起来就是视频啦。

同时,为了避免输出到文件这么麻烦,我们选择把视频输出到屏幕。

打开IDE执行下面的代码:

import cv2 

# 调用摄像头 
cap = cv2.VideoCapture(0) 

while True: 
    # 读取一帧
    flag, frame = cap.read()
    # [可选] 把图像翻转过来,第二个参数为2意为沿y轴翻转
    frame = cv2.flip(frame, 2)
    key = cv2.waitKey(1) & 0xFF

    if flag:
        cv2.imshow("Camera Capture", frame)

    if key is ord('q'): 
        break


cap.release()
cv2.destroyAllWindows() 

 

其中,cv2.waitKey(1)是等待用户按下键盘,如果用户按下了键盘,判断这个键是q键,就退出程序。参数1是waitKey的等待时间。

cv2.imshow为我们提供了一个将图像显示到窗口的功能,它的第一个参数是窗口的标题,第二个参数是窗口显示的图像,窗口的大小会根据图像的大小进行调整。

最后通过cv2.destroyAllWindows()销毁所有窗口。

欢迎大家观赏我的床板(逃

【消失了800年之后突然跳出来】实时手势识别学习笔记0-环境搭建

为了完(ying)成(fu)HCI的项目,报了两位大佬的大腿,打算借此机会学习一下手势识别。

第0篇当然是进行环境搭建了~(因为可以凑出来一篇内容)

好了来看一下项目概要和环境要求:

项目概要

这次的项目是我HCI课程的项目的一部分,主要目的是通过一个摄像头实现实时的手势识别

为什么只写项目的一部分呢?因为项目除了手势识别还要干什么还没想好(逃

有可能是实现一个小游戏吧

咳咳说远了,回到我们的主题

既然是使用摄像头的手势识别,肯定要基于低分辨率(毕竟现在主流的电脑摄像头基本上都是1到2MP的),并且是2D的图像,那么就会想到采用OpenCV来进行录制。

其次,为了进行训练,当然要采用主流的(至少是我很喜欢使用的)Tensorflow框架。

项目环境

既然上面都说到了主要利用到的包,那么就列一下:

  • OpenCV,版本2.4.13.6,是CV2的最新版(截止时间为2018年5月1日,下同)
  • Python,版本3.6.5(采用了Anaconda 3 x64发行版,便于管理更多需要的包)
  • Tensorflow-GPU,版本1.8.0

其他可能用到的包(直接使用Anaconda管理)

因为我的项目还没有完成,我不太确定是否还会增加别的包,就目前来看,使用Anaconda来管理下面的包都是没有问题并且非常方便的(Anaconda是不是考虑给我发点钱?)

  • Numpy,版本1.14.3

    NumPy是Python语言的一个扩充程式库。支援高阶大量的维度阵列与矩阵运算,此外也针对阵列运算提供大量的数学函数函式库。NumPy的前身Numeric最早是由Jim Hugunin与其它协作者共同开发,2005年,Travis Oliphant在Numeric中结合了另一个同性质的程式库Numarray的特色,并加入了其它扩充功能而开发了NumPy。NumPy为开放原始码并且由许多协作者共同维护开发。

注:介绍引用自维基百科,本人不对从其引用的内容的准确性作任何保证,侵删,下同。

  • Matplotlib,版本2.2.2

    matplotlib 是Python编程语言及其数值数学扩展包 NumPy的可视化操作界面。它为利用通用的图形用户界面工具包,如Tkinter, wxPython, Qt或GTK+向应用程序嵌入式绘图提供了应用程序接口(API)。此外,matplotlib还有一个基于图像处理库(如开放图形库OpenGL)的pylab接口,其设计与MATLAB非常类似–尽管并不怎么好用。SciPy就是用matplotlib进行图形绘制。matplotlib最初由John D. Hunter撰写,它拥有一个活跃的开发社区,并且根据BSD样式许可证分发。 在John D. Hunter2012年去世前不久,迈克尔Droettboom被提名为matplotlib的主要开发者。截至到2015年10月30日,matplotlib 1.5.x支持Python 2.7到3.5版本。 Matplotlib 1.2是matplotlib的第一个版本,支持Python 3.x. Matplotlib 1.4是matplotlib支持Python 2.6的最后一个版本。

环境安装

Python (Anaconda)

Anaconda 是一种Python语言的免费增值开源发行版,用于进行大规模数据处理, 预测分析, 和科学计算, 致力于简化包的管理和部署。 Anaconda使用软件包管理系统Conda进行包管理。

Anaconda 的使用非常简单,只需要安装后就可以使用对应的环境。直接从https://www.anaconda.com下载需要的发行版,使用GUI安装即可。本文及其系列均采用Anaconda 3 x64作为默认的Python环境。


Tensorflow

TensorFlow是一个开源软件库,用于各种感知和语言理解任务的机器学习。目前被50个团队用于研究和生产许多Google商业产品,如语音识别、Gmail、Google 相册和搜索,其中许多产品曾使用过其前任软件DistBelief。TensorFlow最初由Google Brain团队开发,用于Google的研究和生产,于2015年11月9日在Apache 2.0开源许可证下发布。

由于我的训练用计算机使用了nVidia GTX 1080 8GB显卡,所以可以采用使用GPU进行计算的Tensorflow框架,使用GPU的好处是速度会快很多(根据我以往经验判断大致可以比CPU版本快两个数量级)。

1. 安装CUDA (限GPU版)

使用GPU版的Tensorflow需要计算机安装有一张以上的nVidia的GPU,并且Compute Capability不低于3.0(可以通过https://developer.nvidia.com/cuda-gpus查看GPU是否符合要求)。

可以看到我的GTX1080的Compute Capability 是6.1,可以使用CUDA和GPU版Tensorflow。

然后安装CUDA9.0。从https://developer.nvidia.com/cuda-90-download-archive下载CUDA 9.0(9.1版本不太确定能否兼容)。安装后在命令行输入

nvcc -V

(注意-V要大写)检查是否安装了CUDA。

nvcc: NVIDIA (R) Cuda compiler driver
Copyright (c) 2005-2017 NVIDIA Corporation
Built on Fri_Nov__3_21:08:12_Central_Daylight_Time_2017
Cuda compilation tools, release 9.1, V9.1.85

根据这个提示信息可以知道我这里已经安装好了9.1.85的CUDA(我的电脑上9.0和9.1是共存的),所以CUDA已经安装完成了。

2. 安装cuDNN(仅限GPU版)

安装完CUDA后还需要安装cuDNN。

访问https://developer.nvidia.com/cudnn后注册nVidia Developer帐号,然后下载对应CUDA版本的cuDNN,将下载解压后的cuda文件夹中的内容放到

X:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\版本号,X是CUDA的安装分区

对应文件夹下即可。

3. 安装Tensorflow(使用pip)

因为安装了Anaconda,所以这一步就很简单了。

打开Anaconda命令行(在开始菜单Anaconda文件夹下),输入:

pip install tensorflow-gpu

即可开始安装。

如果提示更新pip,请根据提示进行操作。在安装过程中有可能需要为pip指定--ignore-installed参数

安装完成后,在命令行下输入

python

进入python环境,然后执行下面的代码测试tensorflow的安装情况:

import tensorflow as tf
hello = tf.constant('hello, world')
sess = tf.Session()
sess.run(hello)
sess.close()

如果你看到类似的输出:

2018-05-08 03:34:12.038568: I T:\src\github\tensorflow\tensorflow\core\platform\cpu_feature_guard.cc:140] Your CPU supports instructions that this TensorFlow binary was not compiled to use: AVX2
2018-05-08 03:34:12.495307: I T:\src\github\tensorflow\tensorflow\core\common_runtime\gpu\gpu_device.cc:1356] Found device 0 with properties:
name: GeForce GTX 1080 major: 6 minor: 1 memoryClockRate(GHz): 1.7335
pciBusID: 0000:06:00.0
totalMemory: 8.00GiB freeMemory: 6.60GiB
2018-05-08 03:34:12.508107: I T:\src\github\tensorflow\tensorflow\core\common_runtime\gpu\gpu_device.cc:1435] Adding visible gpu devices: 0
2018-05-08 03:34:14.156435: I T:\src\github\tensorflow\tensorflow\core\common_runtime\gpu\gpu_device.cc:923] Device interconnect StreamExecutor with strength 1 edge matrix:
2018-05-08 03:34:14.164752: I T:\src\github\tensorflow\tensorflow\core\common_runtime\gpu\gpu_device.cc:929]      0
2018-05-08 03:34:14.168988: I T:\src\github\tensorflow\tensorflow\core\common_runtime\gpu\gpu_device.cc:942] 0:   N
2018-05-08 03:34:14.173767: I T:\src\github\tensorflow\tensorflow\core\common_runtime\gpu\gpu_device.cc:1053] Created TensorFlow device (/job:localhost/replica:0/task:0/device:GPU:0 with 6379 MB memory) -> physical GPU (device: 0, name: GeForce GTX 1080, pci bus id: 0000:06:00.0, compute capability: 6.1)

和:

b'hello'

那么Tensorflow就已经安装完毕了。


OpenCV

起初我打算直接使用Anaconda安装OpenCV,但是没有找到很好的方法,因为Anaconda3默认会安装OpenCV3。所以使用了下面的方法:

访问https://opencv.org/releases.html,下载2.4.13.6下的Win pack,解压后找到cv2.pyd,解压到Anaconda安装目录的

Anaconda安装目录\Lib\site-packages

下,然后进入Anaconda命令行并进入python环境,输入

import cv2

如果没有报错,则OpenCV安装成功。

【自制OS学习笔记| Day 10】更更好的鼠标、窗口、高速计数器和图层优化

更更好的鼠标

  • 完成了昨天的画板后,出现了这样的问题:

  • 正常来说鼠标是可以隐藏在屏幕右边和下边的,但是画板似乎并不能这样……
  • 所以需要完善画面外支持。

画面外支持

  • 修改main:

if (mx > binfo->scrnx - 16) {
    mx = binfo->scrnx - 16;
}
if (my > binfo->scrny - 16) {
    my = binfo->scrny - 16;
}

改成


if (mx > binfo->scrnx - 1) {
    mx = binfo->scrnx - 1;
}
if (my > binfo->scrny - 1) {
    my = binfo->scrny - 1;
}
  • 并且不刷新屏幕外的显示区域:

void sheet_refreshsub(struct SHTCTL *ctl, int vx0, int vy0, int vx1, int vy1){
	int h, bx, by, vx, vy, bx0, by0, bx1, by1;
	unsigned char *buf, c, *vram = ctl->vram;
	struct SHEET *sht;
	/* refresh超出画面范围,进行修正*/
	if (vx0 < 0) { vx0 = 0; }
	if (vy0 < 0) { vy0 = 0; }
	if (vx1 > ctl->xsize) { vx1 = ctl->xsize; }
	if (vy1 > ctl->ysize) { vy1 = ctl->ysize; }
	for (h = 0; h <= ctl->top; h++) {
        // 中略
	}
	return;
}

窗口

  • 已经完成窗口的叠加处理了,所以现在只需要把窗口画出来。(和画鼠标一样一样的)

void make_window8(unsigned char *buf, int xsize, int ysize, char *title){
	static char closebtn[14][16] = {
		"OOOOOOOOOOOOOOO@",
		"OQQQQQQQQQQQQQ$@",
		"OQQQQQQQQQQQQQ$@",
		"OQQQ@@QQQQ@@QQ$@",
		"OQQQQ@@QQ@@QQQ$@",
		"OQQQQQ@@@@QQQQ$@",
		"OQQQQQQ@@QQQQQ$@",
		"OQQQQQ@@@@QQQQ$@",
		"OQQQQ@@QQ@@QQQ$@",
		"OQQQ@@QQQQ@@QQ$@",
		"OQQQQQQQQQQQQQ$@",
		"OQQQQQQQQQQQQQ$@",
		"O$$$$$$$$$$$$$$@",
		"@@@@@@@@@@@@@@@@"
	};
	int x, y;
	char c;
	boxfill8(buf, xsize, COL8_C6C6C6, 0,         0,         xsize - 1, 0        );
	boxfill8(buf, xsize, COL8_FFFFFF, 1,         1,         xsize - 2, 1        );
	boxfill8(buf, xsize, COL8_C6C6C6, 0,         0,         0,         ysize - 1);
	boxfill8(buf, xsize, COL8_FFFFFF, 1,         1,         1,         ysize - 2);
	boxfill8(buf, xsize, COL8_848484, xsize - 2, 1,         xsize - 2, ysize - 2);
	boxfill8(buf, xsize, COL8_000000, xsize - 1, 0,         xsize - 1, ysize - 1);
	boxfill8(buf, xsize, COL8_C6C6C6, 2,         2,         xsize - 3, ysize - 3);
	boxfill8(buf, xsize, COL8_000084, 3,         3,         xsize - 4, 20       );
	boxfill8(buf, xsize, COL8_848484, 1,         ysize - 2, xsize - 2, ysize - 2);
	boxfill8(buf, xsize, COL8_000000, 0,         ysize - 1, xsize - 1, ysize - 1);
	putfonts8_asc(buf, xsize, 24, 4, COL8_FFFFFF, title);
	for (y = 0; y < 14; y++) {
		for (x = 0; x < 16; x++) {
			c = closebtn[y][x];
			if (c == '@') {
				c = COL8_000000;
			} else if (c == '$') {
				c = COL8_848484;
			} else if (c == 'Q') {
				c = COL8_C6C6C6;
			} else {
				c = COL8_FFFFFF;
			}
			buf[(5 + y) * xsize + (xsize - 21 + x)] = c;
		}
	}
	return;
}
  • 继续在main中添加窗口

// 上略
	struct SHEET *sht_back, *sht_mouse, *sht_win;
	unsigned char *buf_back, buf_mouse[256], *buf_win;
// 中略
	init_palette();
	shtctl = shtctl_init(memman, binfo->vram, binfo->scrnx, binfo->scrny);
	sht_back  = sheet_alloc(shtctl);
	sht_mouse = sheet_alloc(shtctl);
	sht_win   = sheet_alloc(shtctl);
	buf_back  = (unsigned char *) memman_alloc_4k(memman, binfo->scrnx * binfo->scrny);
	buf_win   = (unsigned char *) memman_alloc_4k(memman, 160 * 68);
	sheet_setbuf(sht_back, buf_back, binfo->scrnx, binfo->scrny, -1); /* 没有透明色*/
	sheet_setbuf(sht_mouse, buf_mouse, 16, 16, 99);
	sheet_setbuf(sht_win, buf_win, 160, 68, -1); /* 没有透明色*/
	init_screen8(buf_back, binfo->scrnx, binfo->scrny);
	init_mouse_cursor8(buf_mouse, 99);
	make_window8(buf_win, 160, 68, "window");
	putfonts8_asc(buf_win, 160, 24, 28, COL8_000000, "Welcome to");
	putfonts8_asc(buf_win, 160, 24, 44, COL8_000000, "  Haribote-OS!");
	sheet_slide(sht_back, 0, 0);
	mx = (binfo->scrnx - 16) / 2; /* 计算画面中央位置*/
	my = (binfo->scrny - 28 - 16) / 2;
	sheet_slide(sht_mouse, mx, my);
	sheet_slide(sht_win, 80, 72);
	sheet_updown(sht_back,  0);
	sheet_updown(sht_win,   1);
	sheet_updown(sht_mouse, 2);
	sprintf(s, "(%3d, %3d)", mx, my);
	putfonts8_asc(buf_back, binfo->scrnx, 0, 0, COL8_FFFFFF, s);
	sprintf(s, "memory %dMB   free : %dKB",
			memtotal / (1024 * 1024), memman_total(memman) / 1024);
	putfonts8_asc(buf_back, binfo->scrnx, 0, 32, COL8_FFFFFF, s);
	sheet_refresh(sht_back, 0, 0, binfo->scrnx, 48);
// 下略

Haribote-OS是原作者起的名字,没有更改

高速计数器

  • 为了练习使用窗口,写一个高速计数器(顺便引出下面的内容)

void HariMain(void){
	struct BOOTINFO *binfo = (struct BOOTINFO *) ADR_BOOTINFO;
	char s[40], keybuf[32], mousebuf[128];
	int mx, my, i;
	unsigned int memtotal, count = 0;
	struct MOUSE_DEC mdec;
	struct MEMMAN *memman = (struct MEMMAN *) MEMMAN_ADDR;
	struct SHTCTL *shtctl;
	struct SHEET *sht_back, *sht_mouse, *sht_win;
	unsigned char *buf_back, buf_mouse[256], *buf_win;
  
// 中略
    // 前面创建窗口的方法类似
    init_palette();
	shtctl = shtctl_init(memman, binfo->vram, binfo->scrnx, binfo->scrny);
	sht_back  = sheet_alloc(shtctl);
	sht_mouse = sheet_alloc(shtctl);
	sht_win   = sheet_alloc(shtctl);
	buf_back  = (unsigned char *) memman_alloc_4k(memman, binfo->scrnx * binfo->scrny);
	buf_win   = (unsigned char *) memman_alloc_4k(memman, 160 * 52);
	sheet_setbuf(sht_back, buf_back, binfo->scrnx, binfo->scrny, -1);
	sheet_setbuf(sht_mouse, buf_mouse, 16, 16, 99);
	sheet_setbuf(sht_win, buf_win, 160, 52, -1);
	init_screen8(buf_back, binfo->scrnx, binfo->scrny);
	init_mouse_cursor8(buf_mouse, 99);
	make_window8(buf_win, 160, 52, "counter");
	sheet_slide(sht_back, 0, 0);
	mx = (binfo->scrnx - 16) / 2;
	my = (binfo->scrny - 28 - 16) / 2;
	sheet_slide(sht_mouse, mx, my);
	sheet_slide(sht_win, 80, 72);
	sheet_updown(sht_back,  0);
	sheet_updown(sht_win,   1);
	sheet_updown(sht_mouse, 2);
	sprintf(s, "(%3d, %3d)", mx, my);
	putfonts8_asc(buf_back, binfo->scrnx, 0, 0, COL8_FFFFFF, s);
	sprintf(s, "memory %dMB   free : %dKB",
			memtotal / (1024 * 1024), memman_total(memman) / 1024);
	putfonts8_asc(buf_back, binfo->scrnx, 0, 32, COL8_FFFFFF, s);
	sheet_refresh(sht_back, 0, 0, binfo->scrnx, 48);
  
    for (;;) {
        // 在这里进行计数
		count++;
		sprintf(s, "%010d", count);
		boxfill8(buf_win, 160, COL8_C6C6C6, 40, 28, 119, 43);
		putfonts8_asc(buf_win, 160, 40, 28, COL8_000000, s);
		sheet_refresh(sht_win, 40, 28, 120, 44);

		io_cli();
		if (fifo8_status(&keyfifo) + fifo8_status(&mousefifo) == 0) {
			io_sti();
		} else {
// 下略
  • 刷新的问题:

    • 在刷新的时候,总是先刷新范围内的背景图层,再刷新窗口图层,肯定会发生闪烁。

处理闪烁&优化窗口管理

  • 可以认为,在一个窗口的内容变化的时候,其他窗口没有变化,依此进行刷新(因为如果别的窗口也变化的话,可以对变化的窗口依次进行这样的刷新)。
  • 因此,背景层不用刷新。
  • 但是,鼠标需要刷新(窗口的刷新会覆盖鼠标)。
  • 闪烁的实质是渲染和消除的交替。
  • 因此,可以用map的方法进行处理。
  • 另外,为了方便,SHEET本身也记录了管理它的SHTCTL的指针。

struct SHTCTL {
	unsigned char *vram, *map;		// 用一个map来记录
	int xsize, ysize, top;
	struct SHEET *sheets[MAX_SHEETS];
	struct SHEET sheets0[MAX_SHEETS];
};

struct SHTCTL *shtctl_init(struct MEMMAN *memman, unsigned char *vram, int xsize, int ysize){
	struct SHTCTL *ctl;
	int i;
	ctl = (struct SHTCTL *) memman_alloc_4k(memman, sizeof (struct SHTCTL));
	if (ctl == 0) {
		goto err;
	}
	ctl->map = (unsigned char *) memman_alloc_4k(memman, xsize * ysize);	// 初始化map
	if (ctl->map == 0) {
		memman_free_4k(memman, (int) ctl, sizeof (struct SHTCTL));
		goto err;
	}
	ctl->vram = vram;
	ctl->xsize = xsize;
	ctl->ysize = ysize;
	ctl->top = -1; /* 没有SHEET*/
	for (i = 0; i < MAX_SHEETS; i++) {
		ctl->sheets0[i].flags = 0; /* 未使用*/
		ctl->sheets0[i].ctl = ctl; /* 记录归属的SHECTL*/
	}
err:
	return ctl;
}

  • 刷新地图:

void sheet_refreshmap(struct SHTCTL *ctl, int vx0, int vy0, int vx1, int vy1, int h0){
	int h, bx, by, vx, vy, bx0, by0, bx1, by1;
	unsigned char *buf, sid, *map = ctl->map;
	struct SHEET *sht;
	if (vx0 < 0) { vx0 = 0; }
	if (vy0 < 0) { vy0 = 0; }
	if (vx1 > ctl->xsize) { vx1 = ctl->xsize; }
	if (vy1 > ctl->ysize) { vy1 = ctl->ysize; }
	for (h = h0; h <= ctl->top; h++) {
		sht = ctl->sheets[h];
		sid = sht - ctl->sheets0; /* 将进行了减法计算的地址作为图层号码(sheet ID)*/
		buf = sht->buf;
		bx0 = vx0 - sht->vx0;
		by0 = vy0 - sht->vy0;
		bx1 = vx1 - sht->vx0;
		by1 = vy1 - sht->vy0;
		if (bx0 < 0) { bx0 = 0; }
		if (by0 < 0) { by0 = 0; }
		if (bx1 > sht->bxsize) { bx1 = sht->bxsize; }
		if (by1 > sht->bysize) { by1 = sht->bysize; }
		for (by = by0; by < by1; by++) {
			vy = sht->vy0 + by;
			for (bx = bx0; bx < bx1; bx++) {
				vx = sht->vx0 + bx;
				if (buf[by * sht->bxsize + bx] != sht->col_inv) {
					map[vy * ctl->xsize + vx] = sid;
				}
			}
		}
	}
	return;
}
  • 利用map刷新图层

void sheet_refreshsub(struct SHTCTL *ctl, int vx0, int vy0, int vx1, int vy1, int h0, int h1){
	int h, bx, by, vx, vy, bx0, by0, bx1, by1;
	unsigned char *buf, *vram = ctl->vram, *map = ctl->map, sid;
	struct SHEET *sht;
	/* refresh范围超出屏幕,修正*/
	if (vx0 < 0) { vx0 = 0; }
	if (vy0 < 0) { vy0 = 0; }
	if (vx1 > ctl->xsize) { vx1 = ctl->xsize; }
	if (vy1 > ctl->ysize) { vy1 = ctl->ysize; }
	for (h = h0; h <= h1; h++) {
		sht = ctl->sheets[h];
		buf = sht->buf;
		sid = sht - ctl->sheets0;
		/* 计算*/
		bx0 = vx0 - sht->vx0;
		by0 = vy0 - sht->vy0;
		bx1 = vx1 - sht->vx0;
		by1 = vy1 - sht->vy0;
		if (bx0 < 0) { bx0 = 0; }
		if (by0 < 0) { by0 = 0; }
		if (bx1 > sht->bxsize) { bx1 = sht->bxsize; }
		if (by1 > sht->bysize) { by1 = sht->bysize; }
		for (by = by0; by < by1; by++) {
			vy = sht->vy0 + by;
			for (bx = bx0; bx < bx1; bx++) {
				vx = sht->vx0 + bx;
				if (map[vy * ctl->xsize + vx] == sid) {
					vram[vy * ctl->xsize + vx] = buf[by * sht->bxsize + bx];
				}
			}
		}
	}
	return;
}
  • 调用刷新

void sheet_refresh(struct SHEET *sht, int bx0, int by0, int bx1, int by1){
	if (sht->height >= 0) { /* 只对正在显示的图层进行刷新*/
		sheet_refreshsub(sht->ctl, sht->vx0 + bx0, sht->vy0 + by0, sht->vx0 + bx1, sht->vy0 + by1, sht->height, sht->height);
	}
	return;
}

void sheet_slide(struct SHEET *sht, int vx0, int vy0)
{
	struct SHTCTL *ctl = sht->ctl;
	int old_vx0 = sht->vx0, old_vy0 = sht->vy0;
	sht->vx0 = vx0;
	sht->vy0 = vy0;
	if (sht->height >= 0) { /* 只对正在显示的图层进行刷新*/
		sheet_refreshmap(ctl, old_vx0, old_vy0, old_vx0 + sht->bxsize, old_vy0 + sht->bysize, 0);
		sheet_refreshmap(ctl, vx0, vy0, vx0 + sht->bxsize, vy0 + sht->bysize, sht->height);
		sheet_refreshsub(ctl, old_vx0, old_vy0, old_vx0 + sht->bxsize, old_vy0 + sht->bysize, 0, sht->height - 1);
		sheet_refreshsub(ctl, vx0, vy0, vx0 + sht->bxsize, vy0 + sht->bysize, sht->height, sht->height);
	}
	return;
}
  • 最后记得在调整图层高度的时候记得刷新map

void sheet_updown(struct SHEET *sht, int height){
//中略

	/* 重新排列图层*/
	if (old > height) {	/* 比之前的低*/
		if (height >= 0) {
			/* 提高中间的图层*/
            // 中略
			sheet_refreshmap(ctl, sht->vx0, sht->vy0, sht->vx0 + sht->bxsize, sht->vy0 + sht->bysize, height + 1);			// 由于之前对图层信息进行修改使之可以记录SHTCTL,所以不再需要单独传SHTCTL
			sheet_refreshsub(ctl, sht->vx0, sht->vy0, sht->vx0 + sht->bxsize, sht->vy0 + sht->bysize, height + 1, old);
		} else {
			// 中略
			sheet_refreshmap(ctl, sht->vx0, sht->vy0, sht->vx0 + sht->bxsize, sht->vy0 + sht->bysize, 0);
			sheet_refreshsub(ctl, sht->vx0, sht->vy0, sht->vx0 + sht->bxsize, sht->vy0 + sht->bysize, 0, old - 1);
		}
	} else if (old < height) {	/* 比之前的高*/
		if (old >= 0) {
        // 中略
		sheet_refreshmap(ctl, sht->vx0, sht->vy0, sht->vx0 + sht->bxsize, sht->vy0 + sht->bysize, height);
		sheet_refreshsub(ctl, sht->vx0, sht->vy0, sht->vx0 + sht->bxsize, sht->vy0 + sht->bysize, height, height);
	}
	return;
}