Jack Huang's Blog


  • 首页

  • 标签

  • 归档

  • 搜索

使用python3+PyQt5+PyCharm桌面GUI开发

发表于 2019-06-29 | 更新于 2021-03-07

下面简单介绍基于Python3、PyQt5和PyCharm进行桌面GUI开发。

环境配置

安装python3

PyQt5所支持的python版本是从3.5开始的,因此安装的Python3版本必须大于3.5。

安装PyQt5

1
2
pip3 install PyQt5
pip3 install PyQt5-tools

安装PyCharm

安装完PyCharm后,需要配置Qt Designer和PyUIC,前者用于设计UI,后者用于将UI转成Py文件。

具体步骤请参考程序员之路:python3+PyQt5+pycharm桌面GUI开发。

简单示例

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
# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'clearWaterPrintGui.ui'
#
# Created by: PyQt5 UI code generator 5.11.3
#
# WARNING! All changes made in this file will be lost!

from PyQt5 import QtCore, QtGui, QtWidgets

class Ui_Form(object):
def setupUi(self, Form):
Form.setObjectName("Form")
Form.resize(400, 300)
self.label_DirPath = QtWidgets.QLabel(Form)
self.label_DirPath.setGeometry(QtCore.QRect(16, 52, 72, 15))
self.label_DirPath.setObjectName("label_DirPath")
self.label_ExtName = QtWidgets.QLabel(Form)
self.label_ExtName.setGeometry(QtCore.QRect(31, 112, 72, 15))
self.label_ExtName.setObjectName("label_ExtName")
self.buttonBox = QtWidgets.QDialogButtonBox(Form)
self.buttonBox.setGeometry(QtCore.QRect(190, 260, 193, 28))
self.buttonBox.setInputMethodHints(QtCore.Qt.ImhNone)
self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok)
self.buttonBox.setObjectName("buttonBox")
self.lineEdit_DirPath = QtWidgets.QLineEdit(Form)
self.lineEdit_DirPath.setGeometry(QtCore.QRect(100, 50, 281, 21))
self.lineEdit_DirPath.setObjectName("lineEdit_DirPath")
self.lineEdit_ExtName = QtWidgets.QLineEdit(Form)
self.lineEdit_ExtName.setGeometry(QtCore.QRect(100, 112, 281, 21))
self.lineEdit_ExtName.setObjectName("lineEdit_ExtName")

self.retranslateUi(Form)
QtCore.QMetaObject.connectSlotsByName(Form)

def retranslateUi(self, Form):
_translate = QtCore.QCoreApplication.translate
Form.setWindowTitle(_translate("Form", "Form"))
self.label_DirPath.setText(_translate("Form", "文件路径:"))
self.label_ExtName.setText(_translate("Form", "扩展名:"))

# 下面代码是手动添加的
if __name__=="__main__":
import sys
from PyQt5.QtGui import QIcon
app=QtWidgets.QApplication(sys.argv)
widget=QtWidgets.QWidget()
ui=Ui_Form()
ui.setupUi(widget)
# widget.setWindowIcon(QIcon('web.png'))#增加icon图标,如果没有图片可以没有这句
widget.show()
sys.exit(app.exec_())

参考链接

  1. 程序员之路:python3+PyQt5+pycharm桌面GUI开发,by 莫水千流.
  2. 使用PyQt快速开发GUI应用,by Prayer.
  3. PyQt,by wikipedia.
  4. Qt Widgets、QML、Qt Quick的区别,by 云水.
  5. Q_OBJECT宏的作用,by 沈子恒.

Python程序打包成exe

发表于 2019-06-29

大部分人是普通人,不知道如何运行Python脚本程序,因此有必要将Python脚本程序打包成可执行文件,免去安装Python环境,提高Python脚本程序的可用性。

打包方法

Python脚本程序的发布有三种方法:

  • .py文件:对于开源项目或者源码没那么重要的,直接提供源码,需要使用者自行安装Python并且安装依赖的各种库。
  • .pyc文件:有些公司或个人因为机密或者各种原因,不愿意源码被运行者看到,可以使用pyc文件发布,pyc文件是Python解释器可以识别的二进制码,故发布后也是跨平台的,需要使用者安装相应版本的Python和依赖库。
  • 可执行文件:对于非码农用户,最简单的方式就是提供一个可执行文件,只需要把用法告诉他即可。比较麻烦的是需要针对不同平台需要打包不同的可执行文件(Windows, Linux, Mac,…)。

下面介绍将Python脚本程序打包成可执行程序的各种工具。

表1 各种打包工具的对比
Solution Windows Linux OS X Python 3 One file mode Zipfile import Eggs pkg_resources support
bbFreeze yes yes yes no no yes yes yes
py2exe yes no no yes yes yes no no
pyInstaller yes yes yes yes yes no yes no
cx_Freeze yes yes yes yes no yes yes no
py2app no no yes yes no yes yes yes

从上述对Python脚本程序打包工具对比可知,推荐使用pyInstaller。

PyInstaller打包示例

安装PyInstaller

1
pip install pyinstaller

打包Python脚本

打包Python脚本成单独的 .exe 文件。

1
pyinstaller -F yourprogram.py

参考链接

  1. 如何将 Python 程序打包成 .exe 文件?,by 刘哈哈.
  2. Python程序打包成exe可执行文件,by 知行流浪.

Python命令行参数解析示例

发表于 2019-06-29

最新需要一个小程序实现解析命令行参数、遍历指定文件夹,处理指定扩展名的文件。于是简单用python实现一下。

库选择

参数解析

sys.argv

解析Python中命令行参数的最传统的方法是通过sys.argv。但这种方法比较古老,灵活性很差,同时解析出来的参数都是str类型。但在编写简单脚本,参数较少且固定时比较方便。

getopt模块

getopt模块是专门处理命令行参数的模块,用于获取命令行选项和参数,也就是sys.argv。命令行选项使得程序的参数更加灵活。支持短选项模式(-)和长选项模式(–)。

optparse模块

optparse,功能强大,易于使用,可以方便地生成标准的、符合Unix/Posix 规范的命令行说明。但在Python2.7后就已经弃用不再维护。

argparse模块

argparse模块是Python内置的参数解析模块,使用起来比较简单且功能强大。

ArgumentParser类创建时的参数如下:

  • prog - 程序的名字(默认:sys.argv[0])
  • usage - 描述程序用法的字符串(默认:从解析器的参数生成)
  • description - 参数帮助信息之前的文本(默认:空)
  • epilog - 参数帮助信息之后的文本(默认:空)
  • parents - ArgumentParser 对象的一个列表,这些对象的参数应该包括进去
  • formatter_class - 定制化帮助信息的类
  • prefix_chars - 可选参数的前缀字符集(默认:‘-‘)
  • fromfile_prefix_chars - 额外的参数应该读取的文件的前缀字符集(默认:None)
  • argument_default - 参数的全局默认值(默认:None)
  • conflict_handler - 解决冲突的可选参数的策略(通常没有必要)
  • add_help - 给解析器添加-h/–help 选项(默认:True)

add_argument函数的参数如下:

  • name or flags - 选项字符串的名字或者列表,例如foo 或者-f, –foo。
  • action - 在命令行遇到该参数时采取的基本动作类型。
  • nargs - 应该读取的命令行参数数目。
  • const - 某些action和nargs选项要求的常数值。
  • default - 如果命令行中没有出现该参数时的默认值。
  • type - 命令行参数应该被转换成的类型。
  • choices - 参数可允许的值的一个容器。
  • required - 该命令行选项是否可以省略(只针对可选参数)。
  • help - 参数的简短描述。
  • metavar - 参数在帮助信息中的名字。
  • dest - 给parse_args()返回的对象要添加的属性名称。

参数解析模块比较

  • getopt,只能简单的处理命令行参数,无法解析一个参数多个值的情况,如 –file file1 file2 file3。
  • optparse,功能强大,易于使用,可以方便地生成标准的、符合Unix/Posix 规范的命令行说明。但在Python2.7后就已经弃用不再维护。
  • argparse,使其更加容易的编写用户友好的命令行接口。它所需的程序进程了参数定义,argparse将更好的解析sys.argv。同时argparse模块还能自动生成帮助及用户输入错误参数时的提示信息。

文件夹遍历

文件夹遍历有两种方法:

  • 使用os.walk
1
2
3
4
5
6
7
8
9
# -*- coding: utf-8 -*- 
import os
def Test1(rootDir):
list_dirs = os.walk(rootDir)
for root, dirs, files in list_dirs:
for d in dirs:
print os.path.join(root, d)
for f in files:
print os.path.join(root, f)
  • 使用os.listdir
1
2
3
4
5
6
7
8
# -*- coding: utf-8 -*- 
import os
def Test2(rootDir):
for lists in os.listdir(rootDir):
path = os.path.join(rootDir, lists)
print path
if os.path.isdir(path):
Test2(path)

文件处理

Python内置了读写文件的函数,用法和C是兼容的。本节介绍内容大致有:文件的打开/关闭、文件对象、文件的读写等。

代码示例

示例代码如下:

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
# -*- coding: utf-8 -*- 

import os
import argparse

# 遍历文件夹
def traverse(pathName,extName):
extNameList=extName.split(' ')
print(extNameList)
list_dirs = os.walk(pathName)
for root, dirs, files in list_dirs:
for f in files:
targetExt=os.path.splitext(f)[-1]
if targetExt.lower() in extNameList:
clearWaterPrint(os.path.join(root, f))

def clearWaterPrint(fileName):
print(fileName)
fileOrgin=open(fileName,'rb')
fileTarget=open(fileName+'.exe','wb')
fileTarget.write(fileOrgin.read())
fileOrgin.close()
os.remove(fileName)
fileTarget.close()
os.rename(fileName+'.exe',fileName)

# Driver Code
if __name__ == '__main__':
# 首先创建一个ArgumentParser对象
parser = argparse.ArgumentParser(description='Process the water print!')
# 添加--path设置文件目录
parser.add_argument('-p','--path', type = str,dest='pathName', help='give the path of directory',
default = './')
# 添加--ext设置文件目录
parser.add_argument('-e','--ext', type = str,dest='extName', help='give the extension name of file',
default = '.doc .docx .wav .txt .xml .dot .html .jpg .png',nargs = '*')
#返回一个命名空间,如果想要使用变量,可用args.attr
args = parser.parse_args()

# Calling traverse() function
traverse(args.pathName,args.extName)

使用方法如下:

1
2
python3 clearWaterPrint.py -h
python3 clearWaterPrint.py -p /home/test -e .doc .ppt

参考链接

  1. Python的命令行参数解析,by Tyan.
  2. Python中最好用的命令行参数解析工具,by Mingle Wong.
  3. Python遍历文件夹的两种方法比较,by likecao.
  4. 读写字节数据,by python3-cookbook.
  5. 读写二进制文件,by funhacks.

C++标准演化简介

发表于 2019-06-26

最近阅读采用C++编写的MAVROS源码,遇到很多C++语言的新特性,理解起来很费劲,因此,特地分析一下C++标准演变过程,学习其进化过程中引入的新特性,提高C++源码阅读效率。

C++标准演变

2017年12月05日,ISO C++ 委员会正式发布了 C++ 17 标准,官方名称为 ISO/IEC 14882:2017。之前发布的C++标准有C++14、C++11、C++03、C++98。

C++98

C++98是第一个C++标准。它分为两个部分:核心语言和C++标准程序库;后者包含了大部分标准模板库和C标准程序库的稍加修改版本。存在许多不属于标准部分的C++程序库,且使用外部链接,程序库甚至可以用C撰写。

C++标准程序库充分吸收了C标准程序库,并佐以少许的修改,使其与C++良好的运作。另一个大型的程序库部分,是以标准模板库(STL)为基础,STL于1994年2月正式成为ANSI/ISO C++。它提供了实用的工具,如容器类(如:Array和Vector),迭代器(广义指针)提供容器以类似数组的访问方式,以及泛型算法进行搜索和排序的运算。此外还提供了(multi)map和(multi)set,它们都共享相似的成员函数。因此,以下成为可能,使用模板撰写泛型算法,它可以和任何容器或在任何以迭代器定义的序列上运作。如同C,使用#include指令包含标准表头,即可访问程序库里的功能。C++提供69个标准表头,其中19个不再赞成使用。

使用标准模板库(例如:使用std::vector或std::string来取代C风格的数组或字符数组)有助于导向更安全和更灵活的软件。

在STL在纳入C++标准以前,是来自HP和后来的SGI的第三方程式库,标准中并未称之为“STL”,它只是标准库中的一部分,但仍有许多人使用这个名称,以别于其它的标准库(输入/输出流、国际化、诊断、C程序库子集,等等)。 另外,如std::basic_string此类标准委员会添加的接口,有时也被误认为STL;实际上它们并不存在于原始的SGI STL中,在标准化后SGI STL才从标准库吸收加入其中。

C++03

C++03 是 C++ 语言国际标准的一个版本,正式名称是 ISO/IEC 14882:2003。该标准由国际标准化组织(ISO)和国际电工委员会(IEC)共同制定。

C++03 取代了 C++ 标准的前一个版本 C++98,后被 C++11 所取代。C++03 主要是在前一个版本的基础上针对实现方的一些问题进行了修复,从而在各个实现间达到一致、保持了可移植性。该版本共涉及 92 项核心语言缺陷报告、125 项库缺陷报告,所提供的新特性只有一项:值初始化(英语:value initialization)。

C++03 的第 69 号库缺陷报告非常值得一提,为了解决该问题,标准中加入了“std::vector 中的元素必须连续存储”的要求。

C++11

C++11,先前被称作C++0x,即ISO/IEC 14882:2011,是C++编程语言的一个标准。它取代第二版标准ISO/IEC 14882:2003(第一版ISO/IEC 14882:1998公开于1998年,第二版于2003年更新,分别通称C++98以及C++03,两者差异很小),且已被C++14取代。相比于C++03,C++11标准包含核心语言的新机能,而且扩展C++标准程序库,并入了大部分的C++ Technical Report 1程序库(数学的特殊函数除外)。 ISO/IEC JTC1/SC22/WG21 C++标准委员会计划在2010年8月之前完成对最终委员会草案的投票,以及于2011年3月召开的标准会议完成国际标准的最终草案。然而,WG21预期ISO将要花费六个月到一年的时间才能正式发布新的C++标准。为了能够如期完成,委员会决定致力于直至2006年为止的提案,忽略新的提案。最终于2011年8月12日公布,并于2011年9月出版。

2012年2月28日的国际标准草案是最接近于C++11标准的草案,差异仅有编辑上的修正。

像C++这样的编程语言,透过一种演化的的过程来发展其定义。这个过程不可避免地将引发与现有代码的兼容问题,在C++的发展过程中偶尔会发生。不过根据比雅尼·斯特劳斯特鲁普(C++的创始人并且是委员会的一员)表示,新的标准将几乎100%兼容于现有标准。

C++14

C++14是C++的现行标准的非正式名称,正式名称为”International Standard ISO/IEC 14882:2014(E) Programming Language C++”。C++14旨在作为C++11的一个小扩展,主要提供漏洞修复和小的改进。C++14标准的委员会草案(Committee Draft)N3690于2013年5月15日发表。工作草案(Working Draft)N3936已于2014年3月2日完成。最终的投票期结束于2014年8月15日,结果(一致通过)已于8月18日公布。

C++17

C++17又称C++1z,是C++的现行标准的非正式名称,正式名称为”International Standard ISO/IEC Programming Language C++”。C++17旨在作为大型扩展,最终的投票期将于2017年结束。

当前不少著名C++编译器已支持C++17仍未定案的草案(draft),例如最新的GCC6已支持C++ concept的C++事务型内存(Transactional Memory),Visual Studio与Clang当前都提供了modules。

C++新语言特性

Lambda函数与表示式

在标准C++,特别是当使用C++标准程序库算法函数诸如sort和find,用户经常希望能够在算法函数调用的附近定义一个临时的述部函数(又称谓词函数,predicate function)。由于语言本身允许在函数内部定义类别,可以考虑使用函数对象,然而这通常既麻烦又冗赘,也阻碍了代码的流程。此外,标准C++不允许定义于函数内部的类别被用于模板,所以前述的作法是不可行的。

C++11对lambda(即匿名函数)的支持可以解决上述问题。

一个lambda函数可以用如下的方式定义:

1
[](int x, int y) { return x + y; }

这个不具名函数的回返类型是decltype(x+y)。只有在lambda函数匹配”return expression”的形式下,它的回返类型才能被忽略。在前述的情况下,lambda函数仅能为一个述句。

在一个更为复杂的例子中,回返类型可以被明确的指定如下:

1
[](int x, int y) -> int { int z = x + y; return z + x; }

本例中,一个临时的参数z被创建用来存储中间结果。如同一般的函数,z的值不会保留到下一次该不具名函数再次被调用时。

如果lambda函数没有传回值(例如void),其回返类型可被完全忽略。

定义在与lambda函数相同作用域的参数引用也可以被使用。这种的参数集合一般被称作closure(闭包)。

1
2
3
4
5
6
[]      // 沒有定义任何变量。使用未定义变量会引发错误。
[x, &y] // x以传值方式传入(默认),y以引用方式传入。
[&] // 任何被使用到的外部变量都隐式地以引用方式加以引用。
[=] // 任何被使用到的外部变量都隐式地以传值方式加以引用。
[&, x] // x显式地以传值方式加以引用。其余变量以引用方式加以引用。
[=, &z] // z显式地以引用方式加以引用。其余变量以传值方式加以引用。

参考链接

  1. C++,by wikipedia.
  2. C++ 的历史,by cppreference.
  3. C++03,by wikipedia.
  4. C++11,by wikipedia.
  5. C++14,by wikipedia.
  6. C++17,by wikipedia.

有趣的数学游戏

发表于 2019-06-23 | 更新于 2019-06-24

记录一些有趣的数学游戏,与小伙伴分享。

三门问题

问题描述

三门问题(Monty Hall problem)亦称为蒙提霍尔问题、蒙特霍问题或蒙提霍尔悖论,大致出自美国的电视游戏节目Let’s Make a Deal。

问题名字来自该节目的主持人蒙提·霍尔(Monty Hall)。参赛者会看见三扇关闭了的门,其中一扇的后面有一辆汽车,选中后面有车的那扇门可赢得该汽车,另外两扇门后面则各藏有一只山羊。当参赛者选定了一扇门,但未去开启它的时候,节目主持人开启剩下两扇门的其中一扇,露出其中一只山羊。主持人其后会问参赛者要不要换另一扇仍然关上的门。问题是:换另一扇门会否增加参赛者赢得汽车的机率?

问题解答

漫画:反直觉的 “三门问题”

海盗分金币问题

问题描述

经济学上有个“海盗分金”模型:是说5个海盗抢得100枚金币,他们按抽签的顺序依次提方案:首先由1号提出分配方案,然后5人表决,投票要超过半数同意方案才被通过,否则他将被扔入大海喂鲨鱼,依此类推。

“海盗分金”其实是一个高度简化和抽象的模型,体现了博弈的思想。在“海盗分金”模型中,任何“分配者”想让自己的方案获得通过的关键是事先考虑清楚“挑战者”的分配方案是什么,并用最小的代价获取最大收益,拉拢“挑战者”分配方案中最不得意的人们。

问题解答

漫画:有趣的海盗问题

参考链接

  1. 漫画:反直觉的 “三门问题”,by 小灰.
  2. 漫画:有趣的海盗问题,by 小灰.

gdb调试入门

发表于 2019-06-23

GDB是一个由GNU开源组织发布的、UNIX/LINUX操作系统下的、基于命令行的、功能强大的程序调试工具。 对于一名Linux下工作的c++程序员,gdb是必不可少的工具。

启动gdb

对C/C++程序的调试,需要在编译前就加上-g选项。对大型项目,一般选择Debug选项进行编译以 方便gdb调试。

1
$ g++ -g hello.cpp -o hello

调试可执行文件:

1
$gdb <program>

program也就是你的执行文件,一般在当前目录下。

gdb交互命令

启动gdb后,进入到交互模式,通过以下命令完成对程序的调试;注意高频使用的命令一般都会有缩写,熟练使用这些缩写命令能提高调试的效率;

运行

  • run:简记为 r ,其作用是运行程序,当遇到断点后,程序会在断点处停止运行,等待用户输入下一步的命令。
  • continue (简写c ):继续执行,到下一个断点处(或运行结束)
  • next:(简写 n),单步跟踪程序,当遇到函数调用时,也不进入此函数体;此命令同 step 的主要区别是,step 遇到用户自定义的函数,将步进到函数中去运行,而 next 则直接调用函数,不会进入到函数体内。
  • step (简写s):单步调试如果有函数调用,则进入函数;与命令n不同,n是不进入调用的函数的
  • until:当你厌倦了在一个循环体内单步跟踪时,这个命令可以运行程序直到退出循环体。
  • until+行号: 运行至某行,不仅仅用来跳出循环
  • finish: 运行程序,直到当前函数完成返回,并打印函数返回时的堆栈地址和返回值及参数值等信息。
  • call 函数(参数):调用程序中可见的函数,并传递“参数”,如:call gdb_test(55)
  • quit:简记为 q ,退出gdb

设置断点

  • break n (简写b n):在第n行处设置断点
    (可以带上代码路径和代码名称: b OAGUPDATE.cpp:578)
  • b fn1 if a>b:条件断点设置
  • break func(break缩写为b):在函数func()的入口处设置断点,如:break cb_button
  • delete 断点号n:删除第n个断点
  • disable 断点号n:暂停第n个断点
  • enable 断点号n:开启第n个断点
  • clear 行号n:清除第n行的断点
  • info b (info breakpoints) :显示当前程序的断点设置情况
  • delete breakpoints:清除所有断点

查看源代码

  • list :简记为 l ,其作用就是列出程序的源代码,默认每次显示10行。
  • list 行号:将显示当前文件以“行号”为中心的前后10行代码,如:list 12
  • list 函数名:将显示“函数名”所在函数的源代码,如:list main
  • list :不带参数,将接着上一次 list 命令的,输出下边的内容。

打印表达式

  • print 表达式:简记为 p ,其中“表达式”可以是任何当前正在被测试程序的有效表达式,比如当前正在调试C语言的程序,那么“表达式”可以是任何C语言的有效表达式,包括数字,变量甚至是函数调用。
  • print a:将显示整数 a 的值
  • print ++a:将把 a 中的值加1,并显示出来
  • print name:将显示字符串 name 的值
  • print gdb_test(22):将以整数22作为参数调用 gdb_test() 函数
  • print gdb_test(a):将以变量 a 作为参数调用 gdb_test() 函数
  • display 表达式:在单步运行时将非常有用,使用display命令设置一个表达式后,它将在每次单步进行指令后,紧接着输出被设置的表达式及值。如: display a
  • watch 表达式:设置一个监视点,一旦被监视的“表达式”的值改变,gdb将强行终止正在被调试的程序。如: watch a
  • whatis :查询变量或函数
  • info function: 查询函数
  • 扩展info locals: 显示当前堆栈页的所有变量

查询运行信息

  • where/bt :当前运行的堆栈列表;
  • bt backtrace 显示当前调用堆栈
  • up/down 改变堆栈显示的深度
  • set args 参数:指定运行时的参数
1
2
set args -l a -C abc
set args -l=a -C=abc
  • show args:查看设置好的参数
  • info program: 来查看程序的是否在运行,进程号,被暂停的原因。

分割窗口

  • layout:用于分割窗口,可以一边查看代码,一边测试:
  • layout src:显示源代码窗口
  • layout asm:显示反汇编窗口
  • layout regs:显示源代码/反汇编和CPU寄存器窗口
  • layout split:显示源代码和反汇编窗口
  • Ctrl + L:刷新窗口

参考链接

  1. gdb 调试利器,by Linux Tools Quick Tutorial.
  2. GDB调试带参数的程序(转载+整理+实践),by blacet.

随机抽样一致

发表于 2019-06-23

随机抽样一致算法(RANdom SAmple Consensus,RANSAC)。它采用迭代的方式从一组包含离群(outlier)的被观测数据中估算出数学模型的参数。 RANSAC是一个非确定性算法,在某种意义上说,它会产生一个在一定概率下合理的结果,而更多次的迭代会使这一概率增加。此RANSAC算法在1981年由Fischler和Bolles首次提出。

RANSAC的基本假设是

  • “内群”(inlier)数据可以通过几组模型的参数来叙述其分布,而“离群”(outlier)数据则是不适合模型化的数据。
  • 数据会受噪声影响,噪声指的是离群,例如从极端的噪声或错误解释有关数据的测量或不正确的假设。
  • RANSAC假定,给定一组(通常很小)的内群,存在一个程序,这个程序可以估算最佳解释或最适用于这一数据模型的参数。

范例

这里用一个简单的例子来说明,在一组数据点中找到一条最适合的线。假设,此有一组集合包含了内群以及离群,其中内群为可以被拟合到线段上的点,而离群则是无法被拟合的点。如果我们用简单的最小二乘法来找此线,我们将无法得到一条适合于内群的线,因为最小二乘法会受离群影响而影响其结果。而RANSAC,可以只由内群来计算出模型,而且概率还够高。然而,RANSAC无法保证结果一定最好,所以必须小心选择参数,使其能有足够的概率。

包含许多离群的一组数据
包含许多离群的一组数据,要找一条最适合的线。
RANSAC找到的线
RANSAC找到的线,离群值对结果没影响(蓝色点为内群,红色点为离群)

设计

RANSAC算法是一个学习的技巧,通过使用观测数据的随机样本来估计模型参数。RANSAC使用投票机制来寻找优化的拟合结果。每个数据元被用来投票一或多个模型。投票机制基于两点假设:

  • 噪音大的特征并不能一直单独为某个模型投票

  • 有足够多的特征来拟合一个好的模型

一般RANSAC算法由两步骤迭代计算:

  • 一个样本子集,包含数据选取(随机选取)。通过使用这些数据得到一个拟合模型和相关的模型参数。样本子集的数量是最小充分的得到模型参数。

  • 算法检查数据集中的哪些元素是一直在第一步估计到的模型当中的。如果在阈值(相对噪声的最大偏离度)外的话,该模型元素不能拟合估计到的模型便会被当做outlier。

inliers的设置称作“一致性设置”RANSAC算法会一直迭代直到获得足够的inliers。

RANSAC的输入是一些观测数据和一些“可信度”参数,实现步骤:

  1. 随机选择一些原始数据,叫作假设inliers子集
  2. 建立模型拟合
  3. 用其他数据来验证,根据模型特定的loss-function来计算是否符合该模型
  4. 如果足够的点都算是“一致性”设置里则该模型算是好模型
  5. 比较所有的“一致性”设置(就是建立的所有模型)看看哪个inliers多就是我们要的。

参数决定

假设每个点是真正内群的几率是 $w$ :
$$ w = 真正內群的數目 / 數據總共的數量$$

通常我们不知道 $w$ 是多少, $w^n$ 是所选择的n个点都是内群的几率, $1-w^n$ 是所选择的n个点至少有一个不是内群的几率, $(1 − w^n)^k$ 是表示重复k次都没有全部的n个点都是内群的几率, 这边定算法跑k次以后成功的几率是p,那么,

$$ 1 − p =(1 − w^n)^k $$
$$ p = 1 −(1 − w^n)^k $$

所以如果希望成功几率高,p = 0.99, 当n不变时,k越大,p越大, 当w不变时,n越大,所需的k就越大, 通常w未知,所以n选小一点比较好。

应用

RANSAC常被用在计算机视觉,例如,对应点问题和 估算立体摄影机双眼相对点的基本矩阵。

参考链接

  1. 随机抽样一致RANSAC: Random Sample Consensus,by 江知季.
  2. 随机抽样一致,by wikipedia.

尺度空间理论简介

发表于 2019-06-22

尺度空间理论最早可以追溯到1962年的T.Iijima最先提出,学术界开始关注尺度空间技术主要在1986年IEEE PAMI上同时刊出的4篇关于尺度空间理论的文章奠定了发展基础。

现实世界中物体只有具备一定的尺度才能够倍人眼所察觉,计算机视觉学术研究就是在不断的尝试与突破来模拟人眼的观察方法。因此,尺度空间就是试图在图像领域中模拟人眼观察物体的概念与方法。

尺度空间理论

概述

图像的尺度空间是指图像的模糊程度,而非图像的大小。近距离看一个物体和远距离看一个物体,模糊程度是不一样的;从近到远,图像越来越模糊的过程,也是图像的尺度越来越大的过程。

尺度空间的作用

  • 用计算机视觉系统分析未知场景时,计算机并不预先知道图像中物体的尺度。需要同时考虑图像在多尺度下的描述,获知感兴趣物体的最佳尺度;

  • 不同的尺度下都有同样的关键点,那么在不同的尺度的输入图像下就都可以检测出来关键点匹配,也就是尺度不变性;

尺度空间的应用

  • David G. Lowe教授基于描述数字图像局部特征的尺度空间概念,于1999年提出尺度不变特征变换算法(Scale-invariant feature transform,SIFT);
  • 在信息获取与处理、建筑、城市学等不同学科中,都有类似的概念,或有借用尺度空间的概念与方法的可能;

尺度不变特征转换

尺度不变特征转换(Scale-invariant feature transform 或 SIFT)是一种机器视觉的算法用来侦测与描述影像中的局部性特征,它在空间尺度中寻找极值点,并提取出其位置、尺度、旋转不变数,此算法由 David Lowe 在1999年所发表,2004年完善总结。 [1] 后续的论文中也有许多基于 SIFT 改进的论文,例如 SURF 将 SIFT 的许多过程近似,达到加速的效果;PCA-SIFT利用主成分分析降低描述子的维度,减少内存的使用并加快配对速度。

其应用范围包含物体辨识、机器人地图感知与导航、影像缝合、3D模型建立、手势辨识、影像追踪和动作比对。

此算法有其专利,专利拥有者为 英属哥伦比亚大学。

参考链接

  1. SIFT算法系列之尺度空间,by small_munich.
  2. 尺度不变特征转换,by wikipedia.
  3. 尺度空间理论,by ☆Ronny丶.
  4. 特征点检测一,by 张帅宾.
  5. ORB特征提取、匹配及实现,by zhaoxuhui.
  6. 图像特征描述子之BRIEF,by Senit_Co.

面向对象设计的五大原则SOLID

发表于 2019-06-16 | 更新于 2024-03-18

在程序设计领域, SOLID(单一功能、开闭原则、里氏替换、接口隔离以及依赖反转)是由罗伯特·C·马丁在21世纪早期引入,指代了面向对象编程和面向对象设计的五个基本原则。

当这些原则被一起应用时,它们使得一个程序员开发一个容易进行软件维护和扩展的系统变得更加可能。 SOLID所包含的原则是通过引发编程者进行软件源代码的代码重构进行软件的代码异味清扫,从而使得软件清晰可读以及可扩展时可以应用的指南。

SOLID被典型的应用在测试驱动开发上,并且是敏捷开发以及自适应软件开发的基本原则的重要组成部分。

SOLID原则简介

首字母 指代 概念
S 单一功能原则 对象应该仅具有一种单一功能
O 开闭原则 软件体应该是对于扩展开放的,但是对于修改封闭的
L 里氏替换原则 程序中对象在不改变程序正确性的前提下被它的子类所替换
I 接口隔离原则 多个特定客户端接口要好于一个宽泛用途的接口
D 依赖反转原则 依赖于抽象而不是一个实例

单一功能原则

在面向对象编程领域中,单一功能原则(Single responsibility principle)规定每个类都应该有一个单一的功能,并且该功能应该由这个类完全封装起来。所有它的(这个类的)服务都应该严密的和该功能平行(功能平行,意味着没有依赖)。

这个术语由罗伯特·C·马丁(Robert Cecil Martin)在他的《敏捷软件开发,原则,模式和实践》一书中的一篇名为〈面向对象设计原则〉的文章中给出。 马丁表述该原则是基于的《结构化分析和系统规格》一书中的内聚原则(Cohesion)上。

马丁把功能(职责)定义为:“改变的原因”,并且总结出一个类或者模块应该有且只有一个改变的原因。一个具体的例子就是,想象有一个用于编辑和打印报表的模块。这样的一个模块存在两个改变的原因。第一,报表的内容可以改变(编辑)。第二,报表的格式可以改变(打印)。这两方面会的改变因为完全不同的起因而发生:一个是本质的修改,一个是表面的修改。单一功能原则认为这两方面的问题事实上是两个分离的功能,因此他们应该分离在不同的类或者模块里。把有不同的改变原因的事物耦合在一起的设计是糟糕的。

保持一个类专注于单一功能点上的一个重要的原因是,它会使得类更加的健壮。继续上面的例子,如果有一个对于报表编辑流程的修改,那么将存在极大的危险性,因为假设这两个功能存在于同一个类中,修改报表的编辑流程会导致公共状态或者依赖关系的改变,打印功能的代码会因此不工作。

单一职责原则可以参考数据库表的设计,SQL数据库中主从表设计成两个表就是遵循单一职责原则,而NoSQL数据库设计成一个对象则是违反单一职责原则。

示例:

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
abstract class Employee {
// This needs to be implemented
abstract calculatePay (): number;
// This needs to be implemented
abstract reportHours (): number;
// let's assume THIS is going to be the
// same algorithm for each employee- it can
// be shared here.
protected save (): Promise<any> {
// common save algorithm
}
}

class HR extends Employee {
calculatePay (): number {
// implement own algorithm
}
reportHours (): number {
// implement own algorithm
}
}

class Accounting extends Employee {
calculatePay (): number {
// implement own algorithm
}
reportHours (): number {
// implement own algorithm
}

}

class IT extends Employee {
...
}

开闭原则

在面向对象编程领域中,开闭原则规定“软件中的对象(类,模块,函数等等)应该对于扩展是开放的,但是对于修改是封闭的”,这意味着一个实体是允许在不改变它的源代码的前提下变更它的行为。该特性在产品化的环境中是特别有价值的,在这种环境中,改变源代码需要代码审查,单元测试以及诸如此类的用以确保产品使用质量的过程。遵循这种原则的代码在扩展时并不发生改变,因此无需上述的过程。

开闭原则的命名被应用在两种方式上。这两种方式都使用了继承来解决明显的困境,但是它们的目的,技术以及结果是不同的。

开闭原则示意图

图2 开闭原则示意图

开闭原则主要利用面向对象的抽象父类和抽象函数。

里氏替换原则

在面向对象的程序设计中,里氏替换原则(Liskov Substitution principle)是对子类型的特别定义。它由芭芭拉·利斯科夫(Barbara Liskov)在1987年在一次会议上名为“数据的抽象与层次”的演说中首先提出。

里氏替换原则的内容可以描述为: “派生类(子类)对象可以在程式中代替其基类(超类)对象。”

简单理解就是子类可以扩展父类的功能,但不要重写父类的方法,从而确保父子行为的一致性。

接口隔离原则

接口隔离原则(英语:interface-segregation principles, 缩写:ISP)指明客户(client)应该不依赖于它不使用的方法。接口隔离原则(ISP)拆分非常庞大臃肿的接口成为更小的和更具体的接口,这样客户将会只需要知道他们感兴趣的方法。这种缩小的接口也被称为角色接口(role interfaces)。接口隔离原则(ISP)的目的是系统解开耦合,从而容易重构,更改和重新部署。

与类设计的单一职责原则类似,接口也要单一职责,不要太庞大。

举例

以商家接入移动支付API的场景举例,支付宝支持收费和退费;微信接口只支持收费。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
interface PayChannel {
void charge();
void refund();
}

class AlipayChannel implements PayChannel {
public void charge() {
...
}

public void refund() {
...
}
}

class WeChatChannel implements payChannel {
public void charge() {
...
}

public void refund() {
// 没有任何代码
}
}

第二种支付渠道,根本没有退款的功能,但是由于实现了PayChannel,又不得不将refund()实现成了空方法。那么,在调用中,这个方法是可以调用的,实际上什么都没有做!

将PayChannel拆成各包含一个方法的两个接口PayableChannel和RefundableChannel。

依赖反转原则

在面向对象编程领域中,依赖反转原则(Dependency inversion principle,DIP)是指一种特定的解耦(传统的依赖关系创建在高层次上,而具体的策略设置则应用在低层次的模块上)形式,使得高层次的模块不依赖于低层次的模块的实现细节,依赖关系被颠倒(反转),从而使得低层次模块依赖于高层次模块的需求抽象。

该原则规定:

  • 高层次的模块不应该依赖于低层次的模块,两者都应该依赖于抽象接口。
  • 抽象接口不应该依赖于具体实现。而具体实现则应该依赖于抽象接口。

注意:依赖反转原则重要的是利用高层抽象类,而不是底层实现类。

该原则颠倒了一部分人对于面向对象设计的认识方式。如高层次和低层次对象都应该依赖于相同的抽象接口。

依赖反转原则示意图

左图中高层对象A依赖于底层对象B的实现;右图中把高层对象A对底层对象的需求抽象为一个接口A,底层对象B实现了接口A,这就是依赖反转。
图2 依赖反转原则示意图

在传统的应用架构中,低层次的组件设计用于被高层次的组件使用,这一点提供了逐步的构建一个复杂系统的可能。在这种结构下,高层次的组件直接依赖于低层次的组件去实现一些任务。这种对于低层次组件的依赖限制了高层次组件被重用的可行性。

依赖反转原则的目的是把高层次组件从对低层次组件的依赖中解耦出来,这样使得重用不同层级的组件实现变得可能。把高层组件和低层组件划分到不同的包/库(在这些包/库中拥有定义了高层组件所必须的行为和服务的接口,并且存在高层组件的包)中的方式促进了这种解耦。由于低层组件是对高层组件接口的具体实现,因此低层组件包的编译是依赖于高层组件的,这颠倒了传统的依赖关系。众多的设计模式,比如插件,服务定位器或者依赖反转,则被用来在运行时把指定的低层组件实现提供给高层组件。

应用依赖反转原则同样被认为是应用了适配器模式,例如:高层的类定义了它自己的适配器接口(高层类所依赖的抽象接口)。被适配的对象同样依赖于适配器接口的抽象(这是当然的,因为它实现了这个接口),同时它的实现则可以使用它自身所在低层模块的代码。通过这种方式,高层组件则不依赖于低层组件,因为它(高层组件)仅间接的通过调用适配器接口多态方法使用了低层组件,而这些多态方法则是由被适配对象以及它的低层模块所实现的。

举例

1
2
3
4
5
6
7
class PasswordReminder {
private $dbConnection;

public function __construct(MySQLConnection $dbConnection) {
$this->dbConnection = $dbConnection;
}
}

上述代码存在问题:首先MySQLConnection是低层次模块,而PasswordReminder处于高层次,但根据S.O.L.I.D.中D的定义,即依赖抽象而不是具体实现,上面这段代码违反这一原则,PasswordReminder类被迫依赖于MySQLConnection类。

以后如果你改变数据库引擎,你还必须编辑PasswordReminder类,因此违反了开闭原则。

PasswordReminder类不应该关心你的应用程序使用什么数据库,为了解决这个问题我们又一次“对接口编程”,因为高层次和低层次模块应该依赖于抽象,我们可以创建一个接口:

1
2
3
interface DBConnectionInterface {
public function connect();
}

接口有一个connect方法,MySQLConnection类实现该接口,在PasswordReminder类的构造函数不使用MySQLConnection类,而是使用接口替换,不用管你的应用程序使用的是什么类型的数据库,PasswordReminder类可以很容易地连接到数据库,没有任何问题,且不违反OCP。

1
2
3
4
5
6
7
8
9
10
11
12
13
class MySQLConnection implements DBConnectionInterface {
public function connect() {
return "Database connection";
}
}

class PasswordReminder {
private $dbConnection;

public function __construct(DBConnectionInterface $dbConnection) {
$this->dbConnection = $dbConnection;
}
}

根据上面的代码片段,你现在可以看到,高层次和低层次模块依赖于抽象。

迪米特法则

迪米特法则是指软件实体间无需直接通信,那么就不应该发生直接调用,可以通过第三方间接通信。例如软件设计模式当中的门面模式,代理模式。

合成复用原则

合成复用原则是指面向对象设计过程中,尽量使用组合、聚合等关联关系实现,其次才考虑使用继承关系实现。

参考链接

  1. 面向对象的SOLID原则,by wuyuegb2312.
  2. SOLID (面向对象设计),by wikipedia.
  3. SOLID Principles: The Software Developer’s Framework to Robust & Maintainable Code [with Examples],by Khalil Stemmler.
  4. S.O.L.I.D:面向对象设计的头 5 大原则,by 伯乐在线.
  5. 面向对象-软件设计原则-1小时搞懂-波波酱老师,by 波波酱老师.

非专业设计师的基本设计原则

发表于 2019-06-14

绝大部分内容创作者不是专业的设计师,因此在其内容展示时会遇到很大的困难,即如何对展示的内容进行优雅的排版?

遵循如下四个原则,虽然不会让您创造出令人惊艳的设计,但能帮助您创建出色、清晰且易于理解的设计,使每个人都可以轻松理解和互动。

对比

确保所有元素之间有足够的对比度。您的设计元素应该完全相同或显着不同。

对比问题主要表现在4种不同的方面:

  • 颜色:浅色使用深色,反之亦然。

  • 尺寸:仅将彼此相邻的元素放在完全相同的大小或相当大的大小上。

  • 重量:与尺寸相同,只是将元素放在彼此相邻的重量完全相同或重量相当不同的地方。

  • 样式:不要将一个斜体类型放在另一个斜体类型旁边,或者在另一个衬线字体旁边放置一个衬线字体。 结合不同的东西。

一致性

确保类似的元素以类似的方式出现。

为什么? 首先,通过保持事物的一致性(因此,简单),您可以让人们将注意力集中在设计的重要方面,而不是被一直在变化的事物分散注意力。

其次,一致性增加了信任,使事物看起来实际上是设计的,而不是简单地快速抛在一起。

一旦你选择它们就要坚持下去:

  • 字体/字体
  • 调色板/颜色的阴影
  • 表格
  • 对齐
  • 装饰元素的风格

奥卡姆剃刀

奥卡姆剃刀即若无必要,勿增实体,以减少视觉噪音。

您在设计中使用的元素数量越少越好。

为什么? 人类的大脑很难处理信息并在输入过载的情况下做出决策。 使用尽可能少的装饰元素(字体,颜色,阴影,框架,笔画,图标,图案等)。

将奥卡姆剃刀的设计原则应用于所有内容:

如果只用2个元素可以实现某些功能,请不要使用3.如果可以使用10个元素实现某些功能,请不要使用20.您可以使用它。

空间

事物的定位方式会发送关于其含义的元级别消息。

为什么这很重要? 因为了解你如何定位事物以及你在它们周围添加了多少空间有助于降低设计的复杂性,因此,使它更令人愉悦,更容易与之交互。

在设计中使用空间来传达这三个方面的意义:

  1. 接近==相关性

与其他元素相比彼此更接近的事物被认为彼此更多地相互关联而不是与其他元素相关。

这个是最重要的,因为我觉得它经常被忽视(甚至有些人声称是专业设计师,而不是命名任何名字)。

它可以以多种不同的方式应用,例如:

* 行之间应该有一个空格,而不是一行中每个单词之间的空间 - 同样,不同段落之间的空间也比段落内的行之间的空间要大。

* 设计元素应该在彼此之间具有较小的空间,而不是在这些元素和组合物的边缘之间。

* 标签和支持信息应位于其描述/相关的元素附近。

  1. 负空间

与奥卡姆剃刀设计原则结合使用,尽可能地为您的设计提供负面空间,使它们整齐并使其意义更加明显。

将太多元素放入有限的空间就像试着一次听三首不同的歌。 很难理解所说的内容。

  1. 重要性和秩序

最重要的事情,你先放,和/或让它们占用最多的空间。 使用一系列事物来传达秩序。 等等。你绝对可以弄明白其余的。

参考链接

  1. Fundamental design principles for non-designers,by freecodecamp.
  2. 奥卡姆剃刀,by wikipedia.
上一页1…394041…53下一页

Jack Huang

529 日志
69 标签
© 2025 Jack Huang
由 Hexo 强力驱动
|
主题 — NexT.Muse