神经网络与深度学习

0.简介

(1)神经网络,一种美丽的,受生物启发的编程范例,使计算机能够从观测数据中学习
(2)深度学习,用于在神经网络中学习的强大功能集

神经网络和深度学习在为图片,语音识别和自然语言处理中的许多问题提供了解决方法,本文主要内容为神经网络和深度学习的入门书籍,以手写数字识别作为例子。
本文的代码会在Github上展示:???
首先,这是面向新手的入门课程,主要是讲解神经网络和深度学习的原理,所以,代码中并没有使用框架,而是要自己编写,为了易读性,代码并没有进行优化。
其环境为:???

Read More

Pytorch自然语言处理

0.简介

资料来源:
PyTorch自然语言处理(Natural Language Processing with PyTorch)

主要内容:自然语言处理NLP和深度学习,公式部分没有详细的分析。
示例代码:原书中有Spacy模块,但我偏向于NLTK模块来实现

章节:

1.基础介绍
2.传统NLP快速回顾
3.神经网络基础组建
4.自然语言处理前馈网络Feed-Forward Networks
5.Embedding Words and Types
6.自然语言处理Sequence Modeling
7.自然语言处理Sequence Modeling
8.用于自然语言处理的高级Sequence Modeling
9.经典,前沿和后续步骤

1.基础介绍

1.1 有监督学习


如上图所示:
Data:输入数据集中包含下面两个
Observation:观察对象,用x来表示
Targets:与观察对象相对应的标签,用y来表示
Model:数学表达式或者是一个函数,它接收x观察值,并预测目标的标签值。
Parameters:参数化模型中的权重,使用符号为w
Prediction:预测,是模型在给定观察值的情况下,所猜测的目标的标签值,我们通常在其后面加上hat
Loss Function:
比较预测值和观察对象之间的差距,返回一个标量。损失值越低,模型对目标的预测效果越好,用L来表示损失函数。

结合上面的概念,用数学来表示:
一个数据集 D={X[i],y[i]},i=1..n,有n个例子。
给定这个数据集,我们想要学习一个权值w参数化的函数(模型)f。换言之,我们对f的结构做一个假设,给定这个结构,权值w的学习值将充分表征模型。
对于一个给定的输入X,模型预测y_hat作为目标: y_hat = f(X;W)
在监督学习中,对于训练例子,我们知道观察的真正目标标签y。这个实例的损失将为 L(y, y_hat) 。然后,监督学习就变成了一个寻找最优参数/权值w的过程,从而使所有n个例子的累积损失最小化。

1.2 (随机)梯度下降法及反向传播

利用(随机)梯度下降法进行训练,监督学习的目标是为给定的数据集选择参数值,使损失函数最小化。换句话说,这等价于在方程中求最小值。
我们知道梯度下降法是一种常见的求最小值的方法。回忆一下,在传统的梯度下降法中,我们对参数的一些初值进行猜测,并迭代更新这些参数,直到目标函数(损失函数)的计算值低于可接受阈值(即收敛准则)。
对于大型数据集,由于内存限制,在整个数据集上实现传统的梯度下降通常是不可能的,而且由于计算开销,速度非常慢。相反,通常采用一种近似的梯度下降称为随机梯度下降(SGD)。在随机情况下,数据点或数据点的子集是随机选择的,并计算该子集的梯度。当使用单个数据点时,这种方法称为纯SGD,当使用(多个)数据点的子集时,我们将其称为小型批处理SGD。
通常情况下,“纯”和“小型批处理”这两个词在根据上下文变得清晰时就会被删除。在实际应用中,很少使用纯SGD,因为它会由于有噪声的更新而导致非常慢的收敛。一般SGD算法有不同的变体,都是为了更快的收敛。在后面的章节中,我们将探讨这些变体中的一些,以及如何使用渐变来更新参数。这种迭代更新参数的过程称为反向传播。反向传播的每个步骤(又名epoch)由向前传递和向后传递组成。向前传递用参数的当前值计算输入并计算损失函数。反向传递使用损失梯度更新参数。

1.3 观察对象和标签的编码


如上图所示,我们需要用数字来表示观察值(文本),以便与机器学习算法一起使用。
表示文本的一种简单的方法就是用数字向量来表示。我们使用其中一个简单的方法:

1.3.1 One-Hot表示方式

one-hot表示从一个0向量开始,如果单词出现在句子或者文档中,则将向量中相应的条目设置为1,例如下面的两句话:
‘’’bash
Time flies like an arrow
Fruit flies like a banana
‘’’
对句子进行标记,忽略标点符号,然后将所有单词用小写字母来表示,就会得到一个大小为8的词汇表{time, fruit, flies, like, a, an, arrow, banana}。所以,我们可以用一个8维的one-hot向量来表示每一个单词,我们使用l[w]来表示单词w的one-hot表示。

对于短语、句子或者文档,其one-hot表示仅仅是它的组成词语的one-hot表示的逻辑或。

例如,短语’like a banana’的one-hot表示就是一个3×8的矩阵。
通常,会看到折叠形式的或二进制编码,其文本由和词汇表相同的向量表示,用0或1表示缺失或者存在。‘like a banana’的二进制编码是:[0,0,0,1,1,0,0,1]。备注,折叠one-hot是一个向量中有多个1的one-hot

注意,可能对flies的两种意思弄混了,但是语言中充满了这种歧义,但是我们可以通过简单的假设来构建方案。使其学习特定意义的表示,但这个要之后再说。

本文章通常使用one-hot表示,但是在NLP中还有其他的表示方法。

1.3.2 TF表示

句子的TF表示仅仅是句子中词的one-hot总和。
使用前面的one-hot编码,“Fruit flies like time flies a fruit”这句话具有以下TF表示:[1,2,2,1,1,1,0,0]。
每一个数字是句子中出现相应单词的次数

1.3.3 TF-IDF表示

一个文件中可能会出现相同的词语很多次。
例如专利文件中,里面claim,system,method等单词会经常出现很多次。
TF表示方式对更频繁的词进行加权,但是,上面的词语不会增加我们对文章内容的理解,相反,如果类似tetrafluoroethylene这样的词语出现的频率比较低,但是很可能表明了专利文件的性质,所以希望给予它更加大的权重,反文档频率是一种启发式的算法,可以精确地做到这一点。

TF-IDF会降低常见词语的权重,而增加不常见词语的权重。
词频(TF)=某个词在文章中出现的次数 / 文章中的总词数
逆文档频率(IDF)=log(语料库的文档总数 / (包含该词的文档数+1))

TF-IDF=TF(w)*IDF(w)
假如所有文档中都有这个词语,那么数值为0,当一个词语很少出现,可能只出现在一个文档中,那么IDF就是最大值。

在深度学习中,很少看到使用像TF-IDF这样的启发式表示对输入进行编码,因为目标是学习一种表示。
通常,我们从一个使用整数索引的one-hot编码和一个特殊的“embedding lookup”层开始构建神经网络的输入。

1.3.4 目标编码

目标变量的性质取决与所要解决的NLP任务。例如,在机器翻译、摘要和回答问题的情况下,目标是文本,并且使用前面描述的one-hot编码方法进行编码。

许多NLP任务实际上是使用分类标签,其中模型必须预测一组固定标签中的一个。对于这种编码的常见方法是对每一个标签使用唯一的索引。当输出标签的数量太大时,这种简单的表示可能会出现问题。这方面的一个例子是语言建模问题,在这个问题中,任务是预测下一个单词,给定过去看到的单词。标签空间是一种语言的全部词汇,它可以很容易地增长到几十万,包括特殊字符、名称等等。

一些NLP问题涉及从给定文本中预测一个数值。例如,给定一篇英语文章,我们可能需要分配一个数字评分或可读性评分。给定一个餐馆评论片段,我们可能需要预测直到小数点后第一位的星级。给定用户的推文,我们可能需要预测用户的年龄群。有几种方法可以对数字目标进行编码,但是将目标简单地绑定到分类“容器”中(例如,“0-18”、“19-25”、“25-30”等等),并将其视为有序分类问题是一种合理的方法。 这一部分,超过了我这个文章的范围,在这种情况下,目标编码会显著影响性能。

1.4 计算图

将上面的模型对输入进行转换,从而获得预测。损失函数提供反馈信号来调整模型的参数。利用计算图数据结构可以方便地实现该数据流。从技术上讲,计算图是对数学表达式建模的抽象。在深度学习中,计算图的实现(Theano、TensorFlow和PyTorch)进行了额外的记录,以实现在监督学习中训练期间获取参数梯度所需要的自动微分。我们将在PyTorch基础知识中探讨。推理或者预测就是见打的表达式求值,计算图上的正向流。
考虑表达式:y=wx+b
将其写成两个子表达式:z=wx和y=z+b,我们使用一个有向无环图DAG来表示原始的表达式。

之后,我们将看到如何用PyTorch以直观的方式创建计算图形,以及它如何让我们计算梯度,而无需考虑任何记录(bookkeeping)

PyTorch基础:
PyTorch实现了一种“tape-based automatic differentiation”方法,允许我们动态定义和执行计算图形。这对于调试和用最少的努力构建复杂的模型非常有帮助。

动态 VS 静态计算图 像Theano、Caffe和TensorFlow这样的静态框架需要首先声明、编译和执行计算图。虽然这会导致非常高效的实现(在生产和移动设置中非常有用),但在研究和开发过程中可能会变得非常麻烦。像Chainer、DyNet和PyTorch这样的现代框架实现了动态计算图,从而支持更灵活的命令式开发风格,而不需要在每次执行之前编译模型。动态计算图在建模NLP任务时特别有用,每个输入可能导致不同的图结构。

PyTorch是一个优化的张量操作库,它提供了一系列用于深度学习的包。这个库的核心是张量,它是一个包含一些多维数据的数学对象。0阶张量就是一个数字,或者标量。一阶张量(一阶张量)是一个数字数组,或者说是一个向量。类似地,二阶张量是一个向量数组,或者说是一个矩阵。因此,张量可以推广为标量的n维数组,

2.传统NLP快速回顾

自然语言处理(NLP)和计算语言学(CL)是人类语言计算研究的两个领域。NLP旨在开发解决涉及语言的实际问题的方法,如信息提取、自动语音识别、机器翻译、情绪分析、问答和总结。另一方面,CL使用计算方法来理解人类语言的特性。我们如何理解语言?我们如何产生语言?我们如何学习语言?语言之间有什么关系?

在文献中,我们经常看到方法和研究人员的交叉,从CL到NLP,反之亦然。来自语言学习的课程内容可以用来告知NLP中的先验,统计和机器学习的方法可以用来回答CL想要回答的问题。事实上,这些问题中的一些已经扩展到它们自己的学科,如音位学、形态学、句法学、语义学和语用学。

在本书中,我们只关注NLP,但是我们经常根据需要从CL中借鉴思想。在我们将自己完全归属于NLP的神经网络方法之前,有必要回顾一下一些传统的NLP概念和方法。这就是本章的目标。

2.1 语料库Corpora、令牌Tokens、Types

所有的NLP方法,无论是经典的还是现代的,都以文本数据集开始,也称为语料库(复数:corpora)。语料库通常有原始文本(ASCII或UTF-8格式)和与文本相关的任何元数据。原始文本是字符(字节)序列,但是大多数时候将字符分组成连续的称为令牌(Tokens)的连续单元是有用的。在英语中,令牌(Tokens)对应由空格字符或标点分隔的单词和数字序列。

元数据(Metadata)可以是与文本相关联的任何辅助信息,例如标识符,标签和时间戳。 在机器学习术语中,文本及其元数据称为实例(instance)或数据点。 下面是语料库的一组实例,也称为数据集。 鉴于本书重点关注机器学习,我们可以自由地交换术语语料库和数据集。

将文本分为令牌Tokens的过程称为令牌化tokenization。

首先,以下面的推特作为例子:

1
Snow White and Seven Degress #MakeAMovieCold @midnight

上面的#MakeMovieCold标签应该是一个令牌还是4个令牌,许多论文没有太多的关注,许多令牌化决策往往是任意的,但是,这些决策在实践中对准确性的影响比公认的大很多,通常被认为是预处理的繁琐工作,大多数开源NLP包为令牌化提供了合理的支持,举了NLTK和Spacy的例子,这是两个用于文本处理的常用包。

1
2
3
4
5
6
7
8
Input[1]:
from nltk.tokenize import TweetTokenizer
tweet=u"Snow White and the Seven Degrees
#MakeAMovieCold@midnight:-)"
tokenizer = TweetTokenizer()
print(tokenizer.tokenize(tweet.lower()))
Output[1]:
['snow', 'white', 'and', 'the', 'seven', 'degrees', '#makeamoviecold', '@midnight', ':-)']

类型是语料库中的唯一的令牌,语料库中所有类型的集合就是它的词汇表或者词典。词可以分为内容词和停止词。像冠词和介词这样的限定词主要是为了达到语法的目的,就像填充物来支撑着内容词一样。

2.2 Unigrams,Bigrams,Trigrams,…,Ngrams

ngram是文本中出现的固定长度n的连续令牌序列。bigram有两个令牌,unigram只有一个令牌。从文本生成ngram非常简单,但是nltk提供了简单方法。

1
2
3
4
5
6
7
8
def n_grams(text, n):
'''
takes tokens or text, returns a list of n grams
'''
return [text[i:i+n] for i in range(len(text)-n+1)]

cleaned = ['mary', ',', "n't", 'slap', green', 'witch', '.']
print(n_grams(cleaned, 3))

对于子词(subword)信息本身携带有用信息的某些情况,可能需要生成字符ngram。例如,“methanol”中的后缀“-ol”表示它是一种醇;如果您的任务涉及到对有机化合物名称进行分类,那么您可以看到ngram捕获的子单词(subword)信息是如何有用的。在这种情况下,您可以重用相同的代码,除了将每个字符ngram视为令牌。(这里的subword应该是值类似前缀后缀这种完整单词中的一部分)

2.3 Categorizing Sentences and Documents 对文档或句子进行归类

对文档或句子进行归类可能是NLP最早的应用之一,我们在之前的TF和TF-IDF表示中,对于较长的文本块,例如文档或者句子进行分类非常有用。主题标签的分配、评论情绪的检测、垃圾邮件的过滤,语言识别和邮件分类等问题可以定义为受监督的文档分类问题。
还有一种半监督的方法,只用了一个小的部分数据,但是这个方法暂时不考虑。

2.4 对字进行分类 POS 标签

我们可以将标记的概念从文档扩展到单词或者标记。分类词的一个常见的方法是词性标注。

1
2
3
4
5
6
import nltk

document = 'Whether you\'re new to programming or an experienced developer, it\'s easy to learn and use Python.'
sentences = nltk.sent_tokenize(document)
for sent in sentences:
print(nltk.pos_tag(nltk.word_tokenize(sent)))

输出:

1
[('Whether', 'IN'), ('you', 'PRP'), ("'re", 'VBP'), ('new', 'JJ'), ('to', 'TO'), ('programming', 'VBG'), ('or', 'CC'), ('an', 'DT'), ('experienced', 'JJ'), ('developer', 'NN'), (',', ','), ('it', 'PRP'), ("'s", 'VBZ'), ('easy', 'JJ'), ('to', 'TO'), ('learn', 'VB'), ('and', 'CC'), ('use', 'VB'), ('Python', 'NNP'), ('.', '.')]

词性的表达方式:

1
2
3
4
5
6
7
8
9
10
11
12
CC  并列连词          NNS 名词复数        UH 感叹词
CD 基数词 NNP 专有名词 VB 动词原型
DT 限定符 NNP 专有名词复数 VBD 动词过去式
EX 存在词 PDT 前置限定词 VBG 动名词或现在分词
FW 外来词 POS 所有格结尾 VBN 动词过去分词
IN 介词或从属连词 PRP 人称代词 VBP 非第三人称单数的现在时
JJ 形容词 PRP$ 所有格代词 VBZ 第三人称单数的现在时
JJR 比较级的形容词 RB 副词 WDT 以wh开头的限定词
JJS 最高级的形容词 RBR 副词比较级 WP 以wh开头的代词
LS 列表项标记 RBS 副词最高级 WP$ 以wh开头的所有格代词
MD 情态动词 RP 小品词 WRB 以wh开头的副词
NN 名词单数 SYM 符号 TO to

2.5 分块Chunking和浅解析Shallow parsing

浅解析的目的是,推导出由名词、动词、形容词等语法原子组成的高阶的单位,如果没有训练浅解析模型的数据,可以在词性标记上编写正则表达式来近似浅解析。幸运的是,对于英语和最广泛使用的语言来说,这样的数据和预先训练的模型是存在的。

1

2.6 句子树

浅层解析识别短语单位,而识别它们之间关系的任务称为解析(parsing)。例如,用图来表示句子。

解析树(Parse tree)表示句子中不同的语法单元在层次上是如何相关的。另一种可能更有用的显示关系的方法是使用依赖项解析(dependency parsing),如下图表示:

2.7 单词含义

单词有意义,而且通常不止一个。一个词的不同含义称为它的意义(senses)。WordNet是一个长期运行的词汇资源项目,它来自普林斯顿大学,旨在对所有英语单词(嗯,大部分)的含义以及其他词汇关系进行分类。

上面的WordNet项目已经花费了数十年,即使在现在的方法,例如神经网络和深度学习方法的背景下使用现有的语言资源。
词的意义也可以从上下文中归纳出来。从文本中自动发现词义实际上是半监督学习在自然语言处理中的第一个应用。尽管这部分没有详细学习,但资源:Jurasky and Martin(2014),第17章,Manning and Schutze(1999),第7章

参考文献:
Manning, Christopher D., and Hinrich Schütze. (1999). Foundations of statistical natural language processing. MIT press.

Bird, Steven, Ewan Klein, and Edward Loper. (2009). Natural language processing with Python: analyzing text with the natural language toolkit. O’Reilly Media.

Smith, Noah A. (2011). “Linguistic structure prediction.” Synthesis lectures on human language technologies.

Jurafsky, Dan, and James H. Martin. (2014). Speech and language processing. Vol. 3. London: Pearson.

Russell, Stuart J., and Peter Norvig. (2016). Artificial intelligence: a modern approach. Malaysia: Pearson Education Limited.

Zheng, Alice, and Casari, Amanda. (2018). Feature Engineering for Machine Learning: Principles and Techniques for Data Scientists. O’Reilly Media, Inc.

3.神经网络基础组建

这部分的内容可以直接参考我的博客上神经网络与深度学习的基础知识(Emir-Liu.github.io)。

在这个例子中,我们使用Yelp数据集,它将评论与它们的情感标签(正面或负面)配对。此外,我们还描述了一些数据集操作步骤,这些步骤用于清理数据集并将其划分为训练、验证和测试集。

在理解数据集之后,您将看到定义三个辅助类的模式,这三个类在本书中反复出现,用于将文本数据转换为向量化的形式:词汇表(the Vocabulary)、向量化器(Vectorizer)和PyTorch的DataLoader。
词汇表协调我们在“观察和目标编码”中讨论的整数到令牌(token)映射。我们使用一个词汇表将文本标记(text tokens)映射到整数,并将类标签映射到整数。
接下来,矢量化器(vectorizer)封装词汇表,并负责接收字符串数据,如审阅文本,并将其转换为将在训练例程中使用的数字向量。
我们使用最后一个辅助类,PyTorch的DataLoader,将单个向量化数据点分组并整理成minibatches。

3.1 The Yelp Review Dataset

2015年,Yelp举办了一场竞赛,要求参与者根据点评预测一家餐厅的评级。
同年,Zhang, Zhao,和Lecun(2015)将1星和2星评级转换为“消极”情绪类,将3星和4星评级转换为“积极”情绪类,从而简化了数据集。该数据集分为56万个训练样本和3.8万个测试样本。在这个数据集部分的其余部分中,我们将描述最小化清理数据并导出最终数据集的过程。然后,我们概述了利用PyTorch的数据集类的实现。

在这个例子中,我们使用了简化的Yelp数据集,但是有两个细微的区别。第一个区别是我们使用数据集的“轻量级”版本,它是通过选择10%的训练样本作为完整数据集而派生出来的。这有两个结果:首先,使用一个小数据集可以使训练测试循环快速,因此我们可以快速地进行实验。其次,它生成的模型精度低于使用所有数据。这种低精度通常不是主要问题,因为您可以使用从较小数据集子集中获得的知识对整个数据集进行重新训练。在训练深度学习模型时,这是一个非常有用的技巧,因为在许多情况下,训练数据的数量是巨大的。

从这个较小的子集中,我们将数据集分成三个分区:一个用于训练,一个用于验证,一个用于测试。虽然原始数据集只有两个部分,但是有一个验证集是很重要的。在机器学习中,您经常在数据集的训练部分上训练模型,并且需要一个held-out部分来评估模型的性能。如果模型决策基于held-out部分,那么模型现在不可避免地偏向于更好地执行held-out部分。因为度量增量进度是至关重要的,所以这个问题的解决方案是使用第三个部分,它尽可能少地用于评估。

综上所述,您应该使用数据集的训练部分来派生模型参数,使用数据集的验证部分在超参数之间进行选择(进行建模决策),使用数据集的测试分区进行最终评估和报告。

4.自然语言处理前馈网络Feed-Forward Networks

5.Embedding Words and Types

6.自然语言处理Sequence Modeling

7.自然语言处理Sequence Modeling

8.用于自然语言处理的高级Sequence Modeling

9.经典,前沿和后续步骤

Python上GNN网络的简单实现

0.简介

通过python实现原始GNN,数据为自己随机设定的,用于验证原始GNN的模型。

1.具体操作

实现代码中包含下面的部分:
1.1 数据输入
1.2 数据图显示
1.3 模型构建
1.4 模型训练和评估
1.5 画出loss和acc曲线

1.1 数据输入

导入必须的模块,然后输入数据
无向图有多个点,每个点由线连接,然后每个点都有3个label{0,1,2}

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
import os
import json
import scipy
import numpy as np
import networkx as nx
import matplotlib.pyplot as plt

import torch
import torch.nn as nn
import torch.optim as optim
from torch.autograd import Variable

# (node, label)集
N = [("n{}".format(i), 0) for i in range(1,7)] + \
[("n{}".format(i), 1) for i in range(7,13)] + \
[("n{}".format(i), 2) for i in range(13,19)]
# 边集
E = [("n1","n2"), ("n1","n3"), ("n1","n5"),
("n2","n4"),
("n3","n6"), ("n3","n9"),
("n4","n5"), ("n4","n6"), ("n4","n8"),
("n5","n14"),
("n7","n8"), ("n7","n9"), ("n7","n11"),
("n8","n10"), ("n8","n11"), ("n8", "n12"),
("n9","n10"), ("n9","n14"),
("n10","n12"),
("n11","n18"),
("n13","n15"), ("n13","n16"), ("n13","n18"),
("n14","n16"), ("n14","n18"),
("n15","n16"), ("n15","n18"),
("n17","n18")]

1.2 数据图显示

1
2
3
4
5
6
7
8
9
10
11
12
# 构建Graph
G = nx.Graph()
G.add_nodes_from(list(map(lambda x: x[0], N)))
G.add_edges_from(E)
# 设置Graph显示属性,包括颜色,形状,大小
ncolor = ['r'] * 6 + ['b'] * 6 + ['g'] * 6
nsize = [700] * 6 + [700] * 6 + [700] * 6
# 显示Graph
plt.figure(1)
nx.draw(G, with_labels=True, font_weight='bold',
node_color=ncolor, node_size=nsize)
# plt.savefig("./images/graph.png")

1.3 模型构建

首先,从训练函数开始

1
2
3
4
train_loss, train_acc, test_acc = train(node_list=list(map(lambda x:x[0], N)),
edge_list=E,
label_list=list(map(lambda x:x[1], N)),
T=5)

下面是训练的具体内容,在这里,我们需要先补充一下关于torch中的

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
# 开始训练模型
def train(node_list, edge_list, label_list, T, ndict_path="./node_dict.json"):
# 生成node-index字典
if os.path.exists(ndict_path):
with open(ndict_path, "r") as fp:
node_dict = json.load(fp)
else:
node_dict = dict([(node, ind) for ind, node in enumerate(node_list)])
node_dict = {"stoi" : node_dict,
"itos" : node_list}
with open(ndict_path, "w") as fp:
json.dump(node_dict, fp)

# 现在需要生成两个向量
# 第一个向量类似于
# [0, 0, 0, 1, 1, ..., 18, 18]
# 其中的值表示节点的索引,连续相同索引的个数为该节点的度
# 第二个向量类似于
# [1, 2, 4, 1, 4, ..., 11, 13]
# 与第一个向量一一对应,表示第一个向量节点的邻居节点

# 首先统计得到节点的度
Degree = dict()
for n1, n2 in edge_list:
# 边的第一个节点的邻接节点为第二个节点
if n1 in Degree:
Degree[n1].add(n2)
else:
Degree[n1] = {n2}
# 边的第二个节点的邻接节点为第一个节点
if n2 in Degree:
Degree[n2].add(n1)
else:
Degree[n2] = {n1}

# 然后生成两个向量
node_inds = []
node_neis = []
for n in node_list:
node_inds += [node_dict["stoi"][n]] * len(Degree[n])
node_neis += list(map(lambda x: node_dict["stoi"][x],list(Degree[n])))
# 生成度向量
dg_list = list(map(lambda x: len(Degree[node_dict["itos"][x]]), node_inds))
# 准备训练集和测试集
train_node_list = [0,1,2,6,7,8,12,13,14]
train_node_label = [0,0,0,1,1,1,2,2,2]
test_node_list = [3,4,5,9,10,11,15,16,17]
test_node_label = [0,0,0,1,1,1,2,2,2]

# 开始训练
model = OriLinearGNN(node_num=len(node_list),
feat_dim=2,
stat_dim=2,
T=T)
optimizer = torch.optim.Adam(model.parameters(), lr=0.01, weight_decay=0.01)
criterion = nn.CrossEntropyLoss(size_average=True)

min_loss = float('inf')
train_loss_list = []
train_acc_list = []
test_acc_list = []
node_inds_tensor = Variable(torch.Tensor(node_inds).long())
node_neis_tensor = Variable(torch.Tensor(node_neis).long())
train_label = Variable(torch.Tensor(train_node_label).long())
for ep in range(500):
# 运行模型得到结果
res = model(node_inds_tensor, node_neis_tensor, dg_list) # (V, 3)
train_res = torch.index_select(res, 0, torch.Tensor(train_node_list).long())
test_res = torch.index_select(res, 0, torch.Tensor(test_node_list).long())
loss = criterion(input=train_res,
target=train_label)
loss_val = loss.item()
train_acc = CalAccuracy(train_res.cpu().detach().numpy(), np.array(train_node_label))
test_acc = CalAccuracy(test_res.cpu().detach().numpy(), np.array(test_node_label))
# 更新梯度
optimizer.zero_grad()
loss.backward(retain_graph=True)
optimizer.step()
# 保存loss和acc
train_loss_list.append(loss_val)
test_acc_list.append(test_acc)
train_acc_list.append(train_acc)

if loss_val < min_loss:
min_loss = loss_val
print("==> [Epoch {}] : loss {:.4f}, min_loss {:.4f}, train_acc {:.3f}, test_acc {:.3f}".format(ep, loss_val, min_loss, train_acc, test_acc))
return train_loss_list, train_acc_list, test_acc_list
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
# 实现GNN模型
class OriLinearGNN(nn.Module):
def __init__(self, node_num, feat_dim, stat_dim, T):
#在python3中super().xxx可以代替super(class,self).xx
super(OriLinearGNN, self).__init__()
self.embed_dim = feat_dim
self.stat_dim = stat_dim
self.T = T
# torch.nn.Embedding
# torch.nn.Embedding(num_embeddings, embedding_dim, padding_idx=None, max_norm=None, norm_type=2.0,
# scale_grad_by_freq=False, sparse=False, _weight=None)
# 一个简单的查找表,用于存储固定字典和大小的嵌入
# 该模块通常用于存储单元嵌入并使用索引检索它们,模块的输入是索引列表,输出是相应的词嵌入。
# num_embeddings :嵌入字典的大小
# embedding_dim :每个嵌入向量的大小
# padding_idx=None
# max_norm=None
# norm_type=2.0
# scale_grad_by_freq=False
# sparse=False
# _weight=None
# 初始化节点的embedding,即节点特征向量 (V, ln)
self.node_features = nn.Embedding(node_num, feat_dim)
# 初始化节点的状态向量 (V, s)
self.node_states = torch.zeros((node_num, stat_dim))
# 输出层
self.linear = nn.Linear(feat_dim+stat_dim, 3)
self.softmax = nn.Softmax()
# 实现Fw
self.Hw = Hw(feat_dim, stat_dim)
# 实现H的分组求和
self.Aggr = AggrSum(node_num)

# Input :
# X_Node : (N, )
# X_Neis : (N, )
# H : (N, s)
# dg_list: (N, )
def forward(self, X_Node, X_Neis, dg_list):
node_embeds = self.node_features(X_Node) # (N, ln)
neis_embeds = self.node_features(X_Neis) # (N, ln)
X = torch.cat((node_embeds, neis_embeds), 1) # (N, 2 * ln)
# 循环T次计算
for t in range(self.T):
# (V, s) -> (N, s)
H = torch.index_select(self.node_states, 0, X_Node)
# (N, s) -> (N, s)
H = self.Hw(X, H, dg_list)
# (N, s) -> (V, s)
self.node_states = self.Aggr(H, X_Node)
# print(H[1])
out = self.linear(torch.cat((self.node_features.weight, self.node_states), 1))
out = self.softmax(out)
return out # (V, 3)

1.4 模型训练和评估

1.5 画出loss和acc曲线

Python中Panda数据库操作

最近我需要处理大量数据,然后,我想通过panda来实现。

安装很简单,感觉比MySQL小多了。

1
pip install pandas

然后我们来读取数据,读取数据

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
#panda1.py
import pandas as pd

io_out = r'C:\Users\ABC\Desktop\personal\python\panda\list\托外加工单.xlsx'
io_dev = r'C:\Users\ABC\Desktop\personal\python\panda\list\送货清单.xlsx'
io_pur = r'C:\Users\ABC\Desktop\personal\python\panda\list\采购单.xlsx'
io_wee = r'C:\Users\ABC\Desktop\personal\python\panda\list\周计划.xlsx'


out_data = pd.read_excel(io_out,sheet_name = 0,header = 1,index_col = 0)
dev_data = pd.read_excel(io_dev,sheet_name = 0,header = 1,index_col = 1)
pur_data = pd.read_excel(io_pur,sheet_name = 0,header = 1,index_col = 0)
wee_data = pd.read_excel(io_wee,sheet_name = 0,header = 1,index_col = 0)

print('托外加工单')
print(out_data.head())
print('送货清单')
print(dev_data.head())
print('采购单')
print(pur_data.head())


#print('周计划')
#print(wee_data.head())

这里需要注意一下,其中用了pandas.read_excel()函数,在使用的时候会提示需要xlrd模块,这里面有一些参数:
1、io,Excel的存储路径
2、sheet_name,要读取的工作表名称(从0开始排序)
3、header, 用哪一行作列名(从0开始排序)
4、names, 自定义最终的列名
5、index_col, 用作索引的列
6、usecols,需要读取哪些列
7、squeeze,当数据仅包含一列
8、converters ,强制规定列数据类型
9、skiprows,跳过特定行
10、nrows ,需要读取的行数
11、skipfooter , 跳过末尾n行

建议通过命令行来输入,比较方便交互,但是为了方便表达,我写成了文件。

关于pandas中文件的格式,有2种格式:
1.Series
一列数据,
2.DateFrame
由多个列数据组合成的数据帧格式。

那么如何写数据到Excel表格呢:

1
2
3
4
5
6
7
8
9
10
11
import pandas as pd

df = pd.DataFrame({'Hello':['1','2','3'],
'The':['1','2','3'],
'World':['1','2','3']})

writer = pd.ExcelWriter('Hl.xlsx',engine='xlsxwriter')

df.to_excel(writer,sheet_name='tmp')

writer.save()

data.loc[‘index_name’]
通过上面的获取索引中的信息,例如:

1
data.loc[:,['产品名','母件特征','制造数量','预交日','日期']]

通过位置来获取信息:

1
data.iloc[3:5,0:2]

判断表中的数据是否在另一个表中存在的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

from pandas import Series,DataFrame
data = {'language':['Java','PHP','Python'],'year':[1995,1995,1991]}
frame = DataFrame(data)
frame['IDE'] = Series(['Intellij','Notepad','IPython'])

'IPython' in frame['IDE']
#输出False

3 in frame['IDE']
#输出True

#猜测 'IPython' in frame['IDE'] <=> 'IPython' in frame['IDE'].index
#所以理所当然会输出False
#注:并不意味着frame['IDE'] == frame['IDE'].index,只是说两句代码在in中效果差不多

#正确判断方法
'IPython' in frame['IDE'].values
#输出True

布尔索引
通过一个简单的列的数值来挑选数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
In [39]: df[df['A'] > 0]
Out[39]:
A B C D
2013-01-01 0.469112 -0.282863 -1.509059 -1.135632
2013-01-02 1.212112 -0.173215 0.119209 -1.044236
2013-01-04 0.721555 -0.706771 -1.039575 0.271860

In [40]: df[df > 0]
Out[40]:
A B C D
2013-01-01 0.469112 NaN NaN NaN
2013-01-02 1.212112 NaN 0.119209 NaN
2013-01-03 NaN NaN NaN 1.071804
2013-01-04 0.721555 NaN NaN 0.271860
2013-01-05 NaN 0.567020 0.276232 NaN
2013-01-06 NaN 0.113648 NaN 0.524988

使用isin()方法来过滤

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
In [41]: df2 = df.copy()

In [42]: df2['E'] = ['one', 'one', 'two', 'three', 'four', 'three']

In [43]: df2
Out[43]:
A B C D E
2013-01-01 0.469112 -0.282863 -1.509059 -1.135632 one
2013-01-02 1.212112 -0.173215 0.119209 -1.044236 one
2013-01-03 -0.861849 -2.104569 -0.494929 1.071804 two
2013-01-04 0.721555 -0.706771 -1.039575 0.271860 three
2013-01-05 -0.424972 0.567020 0.276232 -1.087401 four
2013-01-06 -0.673690 0.113648 -1.478427 0.524988 three

In [44]: df2[df2['E'].isin(['two', 'four'])]
Out[44]:
A B C D E
2013-01-03 -0.861849 -2.104569 -0.494929 1.071804 two
2013-01-05 -0.424972 0.567020 0.276232 -1.087401 four

设置新列将会自动按照索引顺序添加

1
2
3
4
5
6
7
8
9
10
11
12
13
In [45]: s1 = pd.Series([1, 2, 3, 4, 5, 6], index=pd.date_range('20130102', periods=6))

In [46]: s1
Out[46]:
2013-01-02 1
2013-01-03 2
2013-01-04 3
2013-01-05 4
2013-01-06 5
2013-01-07 6
Freq: D, dtype: int64

In [47]: df['F'] = s1

通过标签设置数值

1
In [48]: df.at[dates[0], 'A'] = 0

通过位置设置数值

1
In [49]: df.iat[0, 1] = 0

通过NumPy数组来设置数值

1
In [50]: df.loc[:, 'D'] = np.array([5] * len(df))

有趣的操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
In [52]: df2 = df.copy()

In [53]: df2[df2 > 0] = -df2

In [54]: df2
Out[54]:
A B C D F
2013-01-01 0.000000 0.000000 -1.509059 -5 NaN
2013-01-02 -1.212112 -0.173215 -0.119209 -5 -1.0
2013-01-03 -0.861849 -2.104569 -0.494929 -5 -2.0
2013-01-04 -0.721555 -0.706771 -1.039575 -5 -3.0
2013-01-05 -0.424972 -0.567020 -0.276232 -5 -4.0
2013-01-06 -0.673690 -0.113648 -1.478427 -5 -5.0

缺失的数据
pandas使用np.nan来代表缺失的数据,默认情况下,它不包含在计算中。
重新索引允许你修改/增加/删除指定的索引,这将返回数据的一个副本。

1
2
3
4
5
6
7
8
9
10
11
In [55]: df1 = df.reindex(index=dates[0:4], columns=list(df.columns) + ['E'])

In [56]: df1.loc[dates[0]:dates[1], 'E'] = 1

In [57]: df1
Out[57]:
A B C D F E
2013-01-01 0.000000 0.000000 -1.509059 5 NaN 1.0
2013-01-02 1.212112 -0.173215 0.119209 5 1.0 1.0
2013-01-03 -0.861849 -2.104569 -0.494929 5 2.0 NaN
2013-01-04 0.721555 -0.706771 -1.039575 5 3.0 NaN

Python中GUI编程tkinker

Python的GUI编程 tkinter

环境:
Python3

https://www.tutorialspoint.com/python/python_gui_programming.htm
Python提供了多个图形开发界面的库,其中最主要的是:

tkinter
Python内嵌的一个GUI,可以在大多数Unix平台上使用,也可以在Windows和Mac系统中运行

wxPython
开源软件

Jython
可以和Java无缝集成,除了一些标准模块,Jython 使用 Java 的模块。Jython 几乎拥有标准的Python 中不依赖于 C 语言的全部模块。

通过tkinter来创造界面程序非常方便,只要下面的步骤:
1.导入tkinter模块
2.创造图形界面主窗口
3.增加一个或者多个小部件
4.进入主要事件循环,对用户引起的事件采取反馈

tkinter部件包含:
1.按钮
2.画布
3.复选按钮
4.输入
5.框架
6.标签(finished)
7.列表框
8.菜单按钮
9.菜单
10.信息
11.单选按钮
12.比例
13.滚动条
14.文本
15.顶层
16.旋转框
17.窗格视窗
18.标签框
19.tk消息框

标准属性
1.外形尺寸
2.颜色
3.字体
4.锚点
5.浮雕样式
6.位图
7.光标

几何管理器
1.pack()方法 这个几何管理器在将小部件放在父部件之前先将它们按块的方式组织起来(finished)
2.grid()方法 这个几何管理器在父部件中按照表状结构组织小部件
3.place()方法 这个几何管理器将小部件放置在父部件的特定位置中

下面的内容将会涉及上面的内容,但是,我们不会一个一个看下来,学习就先做一些有趣的东西吧。

GUI计算器,这是入门的项目来熟悉tkinter,项目很简单,但是可以有很多写法:

首先,来一个窗口

1
2
3
4
5
6
7
8
#test0.py
from tkinter import *

window = Tk()
window.title("helloGUI")
window.geometry('1068x681+10+10')
window.mainloop()

上面就是简单的导入模块,然后建立窗口,设置标题,窗口尺寸,进入事件循环。

开始第一步,按钮和标签。
通过标签来显示内容和反馈,按钮作为输入。
标签:
这个部件可以使用一个框来显示文字或者图片。上面的标签可以在任何时候修改。
语法:

1
w = Label(master,option=value,...)

变量:
master:表示父窗口
options:下面是这个部件常用的选项列表。这些选项可以用逗号分隔的键值来表示。

1.anchor 如果窗口小部件的空间超过了文本所需的空间,则此选项控制文本的位置。默认值为anchor = CENTER,它将文本在可用空间中居中。
2.bg 正常背景色显示在标签和指示器后面。
3.bitmap 将此选项设置为等于bitmap或图像对象,标签将显示该图形。
4.bd 指标周围边框的大小。默认值为2像素。
5.cursor 如果将此选项设置为光标名称(arrow,dot等),则当光标位于复选按钮上方时,鼠标光标将变为该样式。
6.font 如果要在此标签中显示文本(使用text或textvariable选项,则font选项指定将以哪种字体显示文本。)
7.fg 如果要在此标签中显示文本或位图,则此选项指定文本的颜色。如果要显示位图,则此颜色将显示在位图中1的位置。
8.height 新框架的垂直尺寸。
9.image 要在标签窗口小部件中显示静态图像,请将此选项设置为图像对象。
10.justify 指定多行文本彼此之间如何对齐:LEFT表示向左对齐,CENTER表示居中(默认),RIGHT表示右对齐。
11.padx 小部件内的文本左右添加了额外的空间。默认值为1。
12.pady 在小部件中的文本上方和下方添加了额外的空间。默认值为1。
13.relief 指定标签周围装饰性边框的外观。默认值为FLAT;其他值。
14.text 要在标签窗口小部件中显示一行或多行文本,请将此选项设置为包含文本的字符串。内部换行符(“ \ n”)将强制换行。
15.textvariable 要将标签窗口小部件中显示的文本从属于StringVar类的控制变量,请将此选项设置为该变量。
16.underline 通过将此选项设置为n,可以在文本的第n个字母下方显示下划线(_),从0开始计数。默认值为underline = -1,表示没有下划线。
17.width 标签的宽度,以字符为单位(不是像素!)。如果未设置此选项,则标签将调整大小以适合其内容。
18.wraplength 通过将此选项设置为所需的数字,可以限制每行中的字符数。默认值为0,表示仅在换行符处才断开行。

但是,有一个小小的问题,你在尝试上面的函数之后,标签并没有显示。我们还需要一个函数widget.pack()。

这是几何管理器的一种方法,将小部件放置在父部件上,如果没有这个函数,小部件也就不会显示了。

这里有一个需要注意的地方,那么就是textvariable中提到的StringVar变量。
在之前了解到,string字符串是不可变量,但是这里的StringVar是变量。
这里就要提到Varible类了,有些控件,例如Entry、Radiobutton等,可以通过传入特定的参数和程序变量绑定,这些参数包括:
variable、textvariable、onvalue、offvalue、value。

这种绑定时双向的,如果该变量发生改变,那么与该变量绑定的空间也会随之更新。但是一般的Python变量不能够传递给这些参数中。

定义:
x = StringVar()
x = IntVar()
x = DoubleVar()
x = BooleanVar()

获取,使用get()方法
设置,使用set()方法

我们来讨论一下几何管理器的pack()函数。
语法:

1
widget.pack(pack_options)

下面是一些可选项:
1.expand 当设置为true,小部件将会扩展,从而填充小部件父部件中没有使用的任何空间。
2.fill 决定小部件是否填充打包文件分配给它的任何额外空间,还是保持自己的最小尺寸:NONE(默认)、Y(垂直填充)、BOTH(水平和垂直填充)
3.side 确定小部件在父部件的那一侧包装:TOP(默认)、BOTTOM、LEFT、RIGHT。

输出和基础的几何管理完成了,那么我们来到输出环节,开始学习按钮这一部分:

按钮是在Python应用程序中添加按钮,这些按钮可以显示文字或者图片来传达这些按钮的目的。你可以将一个函数或者方法附在按钮上,当你点击按钮的时候,调用。

语法:

1
w = Button(master,option=value,...)

和之前的标签一样:
master 代表父窗口
option 可选的部件选项,常用的如下,我感觉这些选项其实和前面的有些类似,随便看看:
1.activebackground 按钮位于光标下方时的背景色。
2.activeforeground 按钮位于光标下方时的前景色。
3.bd 边框宽度(以像素为单位)。默认值为2。
4.bg 正常背景色。
5.command 单击按钮时要调用的函数或方法。
6.fg 普通前景(文本)颜色。
7.font 用于按钮标签的文本字体。
8.height 按钮的高度,以文本行(用于文本按钮)或像素(用于图像)表示。
9.highlightcolor 当窗口小部件具有焦点时,焦点的颜色突出显示。
10.image 要在按钮上显示的图像(而不是文本)。
11.justify 如何显示多行文本:LEFT左对齐每行;CENTER将它们居中;或RIGHT右对齐。
12.padx 文本左侧和右侧的其他填充。
13.pady 文本上方和下方的其他填充。
14.relief 指定边框的类型。其中一些值为SUNKEN,RAISED,GROOVE和RIDGE。
15.state 将此选项设置为DISABLED可使按钮变灰并使它无响应。鼠标悬停在按钮上时,其值为ACTIVE。默认值为NORMAL。
16.underline 默认值为-1,表示该按钮上的文本的任何字符都不会带有下划线。如果为非负数,则相应的文本字符将带有下划线。
17.width 按钮的宽度,以字母(如果显示文本)或像素(如果显示图像)为单位。
18.wraplength 如果将此值设置为正数,则文本行将被包装以适合此长度。

方法:
1.flash() 使按钮在活动颜色和正常颜色之间闪烁几次。使按钮保持原来的状态。如果该按钮被禁用,忽视这个函数。
2.invoke() 调用按钮的回调,并返回该函数返回的内容。如果按钮被禁用或没有回调,则无效。

那么试一试:

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
#test1.py
from tkinter import *
import tkinter.messagebox as msg

window = Tk()
window.title("helloGUI")
window.geometry('1068x681+10+10')
#建立窗口,设置标题,窗口初始大小

label_text = StringVar()
label_text.set('Hello the GUI')
#label = Label(window,text='Hello the GUI')
label = Label(window,textvariable = label_text)
#建立标签label

def click_button():
#label.config(text = 'Yes')
label_text.set('Yes')
msg.showinfo('Hello Button','Hello')

button = Button(window,text='click it',command = click_button)

label.pack()
button.pack()
#几何控制,控制部件的位置

window.mainloop()
#进入主事件循环

上面有两种修改text的方法,一种是用textvariable来修改,另一种是用config()来修改
然后,我还是用了messagebox函数来输出,这个将会在之后进行补充。

我们已经初步使用了按钮和标签,那么我们就可以建立一个计算器了。

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
#test2.py
from tkinter import *
import tkinter.messagebox as msg

window = Tk()
window.title("Calculator")
window.geometry('1068x681+10+10')
#建立窗口,设置标题,窗口初始大小

label_text = StringVar()
label_text.set('Hello The Calculator')
in_put = ''
str_num = '0'
pre_num = 0
number = 0
pre_ope= 0
label = Label(window,textvariable = label_text)
#建立标签label

def deal(dat):
global in_put,str_num
in_put += dat
label_text.set(in_put)
if dat.isdigit():
str_num += dat
else:
global number,pre_num,pre_ope
number = int(str_num)
msg.showinfo('str_num',str_num)
msg.showinfo('number',str(number))
msg.showinfo('pre_num',str(pre_num))
str_num = '0'
if dat == '=':
in_put = ''
if pre_ope == 1:
number += pre_num
pre_number = number
msg.showinfo('Answer',number)
elif dat == '+':
pre_num = number
number = 0
pre_ope = 1
#建立数据处理函数

def click_1():
tmp='1'
deal(tmp)
def click_add():
tmp='+'
deal(tmp)
def click_equ():
tmp='='
deal(tmp)
#建立按键函数

button_1 = Button(window,text='1',command = click_1)
button_add = Button(window,text='+',command = click_add)
button_equ = Button(window,text='=',command = click_equ)
#建立按钮

label.pack()
button_1.pack()
button_add.pack()
button_equ.pack()
#几何控制,控制部件的位置

window.mainloop()
#进入主事件循环

上面的程序中,只有1和+和=三个按钮,但是已经可以进行运算过程。过程中通过showinfo来显示中间变量。

但是,如果要完成一个完整的计算器就太复杂了。

Ubuntu上apt抽风出现的问题及修补

apt抽风了怎么办?
先哭。。
然后资料来源:https://blog.csdn.net/shimadear/article/details/90598646

下面是详细:
apt下载出现下面的错误:
Could not get lock /var/lib/dpkg/lock-frontend - open (11: Resource temporarily unavailable)

解决办法:
第一种情况:
进程中存在与apt相关的正在运行的进程:
首先检查是否在运行apt,apt-get相关的进程

1
ps aux | grep -i apt

如果存在与apt相关的正在运行的进程,kill掉进程;

1
sudo kill -9 <process id>

或者直接简单粗暴的:

1
sudo killall apt apt-get

如果进行完上面的步骤还是无法顺利执行apt-get 操作,则属于第二种情况:

第二种情况:
进程列表中已经没有与apt相关的进程在运行,但依然报错,在这种情况下,产生错误的根本原因是lock_file. lock_file用于防止两个或多个进程使用相同的数据。 当运行apt或apt-commands时,它会在几个地方创建lock files。 当前一个apt命令未正确终止时,lock file未被删除,因此它们会阻止任何新的apt / apt-get命令实例,比如正在执行apt-get upgrade,在执行过程中直接ctrl+c取消了该操作,很有可能就会造成这种情况。
要解决此问题,首先要删除lock file。
使用lsof命令获取持有lock file的进程的进程ID,依次运行如下命令:

1
2
3
lsof /var/lib/dpkg/lock
lsof /var/lib/apt/lists/lock
lsof /var/cache/apt/archives/lock

需要注意的是,以上命令执行结果如果无返回,说明没有正在运行的进程;如果返回了相应的进程,需要kill掉。

删除所有的lock file

1
2
3
sudo rm /var/lib/apt/lists/lock
sudo rm /var/cache/apt/archives/lock
sudo rm /var/lib/dpkg/lock

最后重新配置一下dpkg:

1
sudo dpkg --configure -a

如果上述命令不出任何错误,就万事大吉了。(我是到这里问题就解决了)

但是有时候,生活总是嫌你不够惨,执行配置命令时可能会出现以下错误:

dpkg: error: dpkg frontend is locked by another process
这需要我们额外进行一些操作:

找出正在锁定lock file的进程:

1
2
3
4
5
6
lsof /var/lib/dpkg/lock-frontend
kill掉输出的进程(如果输出为空则忽略)
sudo kill -9 PID
删除lock file并重新配置dpkg:
sudo rm /var/lib/dpkg/lock-frontend
sudo dpkg --configure -a

Ubuntu上opencv教程5之特征检测及描述

0 简介

特征检测及描述内容:

  1. 了解功能
    图像的主要特征是什么。这些特征对我们有什么用处。
  2. (Harris)哈里斯角检测
    拐角是一个很好的特征,我们如何找到它们。
  3. Shi-Tomasi拐角检测和良好的跟踪功能
  4. SIFT(Scale-Invariant Feature Transform)尺度不变特征变换
    当图像比例改变时,Harris角检测不够好。为此出现了SIFT方法。
  5. SURF(Speeded-Up Robust Features)加速鲁棒特征
    为了提高SIFT运算速度
  6. 用于角点检测的快速算法
    上面的特征检测分别有优势。但是它们在实时应用例如SLAM中的处理速度不够快。所以引入了运算速度更快的快速算法。
  7. BRIEF(Binary Robust Independent Elementary Features)二进制健壮的独立基本特征
    SIFT使用了一个包含128个浮点数的特征描述。考虑到大量的特征。它将会花费大量内存和时间来匹配。我们可以表达它来使他更快。但是我们首先依然需要计算。BRIEF会使用更少的内存和更快的匹配来查找二进制特征。
  8. ORB(Oriented FAST and Rotated BRIEF)定向快速和旋转BRIEF
    首先吐槽一下,这个名字的缩写里面又包含了一个缩写,还好里面的缩写上面刚好有,完整的名字实在是太长了。
    SIFT和SURF各有利弊,但是如果你的应用中,每年都为使用他们花钱呢?他们都有专利。为了解决这个问题,opencv提供了一个全新的免费的SIFT和SURF代替品,那就是ORB。
  9. 特征匹配
    我们了解了大量关于特征检测和描述。现在开始匹配不同的描述,opencv提供了两种技术:Brute-Force和基于FLANN的匹配。
  10. 特征匹配和单应性查找对象
    现在我们了解了特征匹配,我们通过calib3d模块来在复杂的图片中查找物品。

1.了解特征

这部分很简单,和标题一样,了解特征。

就像拼图游戏一样,得到图片中的很多小的图片块,然后将他们正确的结合到一起,组成一个大的完整的实际图片。那该怎么做?

将这个理论投影到计算机程序中,使计算机来玩拼图游戏。将这个话题进行延伸,我们是否可向计算机提供大量的自然图片,然后让计算机将图片组合为一个大的单个图片?继续延伸,将多个图片拼接到一起,那么是否可以提供大量相关结构的图片,然后让计算机创建3D模型?

那么回归到最开始的问题。如何玩拼图游戏?

答案是:寻找独特的特定模式或者特定特征,这些特征可以轻松跟踪和比较。如果我们对其进行定义,很难通过语言来表达,但是我们可以知道它们是什么。即使小孩子也可以指出图片中的某些特征。在游戏中,我们在图像中搜索这些特征,然后找到他们,然后在其他图像中找到相同的特征,将他们对齐。

那么,这个问题更加具体,这些特征是什么。

人类发现这些特征,这一个过程已经在大脑中进行了编程。但是,如果我们深入研究某些图片并且搜索不同的样式,我们会有一些有趣的特点。

首先,如果你所搜寻的特征是一个平坦区域,那么这些特征你很难在图片中找到。可以知道大概的位置,但是很难找到准确的位置。

当特征是物品的边缘,就可以找到一个大概的位置,但是准确的位置依然有些困难。因为沿着边缘,到处是不同的。虽然和平坦区域相比,边缘有更好的功能,但是依然不够准确。

当你搜寻的图片是某些物体的角落,那么就很容易找到他们。因为在拐角处,将这部分图片移动到任意位置,图片都会有所不同。所以,他们有很好的功能。

所以,总结一下,拐点被认为是图片中的良好的特征,在某些情况下,斑点也是一种很好的特征。

那么,我们可以回答上面的问题。
寻找特征就是寻找图像中在其周围的所有区域中(少量移动)时变化最大的区域。

寻找特征被称为特征检测。

那么,假如我们在图像中找到了特征,那么,我们需要在其他图像中找到相同的内容。我们该怎么做?

我们围绕该特征选取一个区域,用自己的话语来解释这个特征,例如,颜色大小等特征,然后在其他图像中搜索相同的区域,来在其他图像中找到他。这些表述称之为功能描述。获得功能描述后,可以在所有的图像中找到相同的功能并且将他们对齐或者缝合等操作。

在这个模式里,我们用opencv中的不同算法来查找功能,进行描述,进行匹配。

2. (Harris)哈里斯角检测

2.1 理论

首先,我们可以知道角就是图像中各个方向上强度变化很大的区域。
1988年Chris Harris和Mike Stephens在论文组合式拐角和边缘检测器中尝试找到这些拐角。也就是哈里斯拐角检测器。然后形成了一个数学形式。
https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_feature2d/py_features_harris/py_features_harris.html
上面是部分证明过程。

2.2 opencv中哈里斯角检测

Opencv中有cv2.cornerHarris()函数,里面有变量:
img
输入图片,灰度图而且为float32类型。
blockSize
角检测附近区域的大小
ksize
使用的Sobel导数的孔径参数
k
方程中的哈里斯检测器自由参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import cv2 
import numpy as np

filename = 'chessboard.png'
img = cv2.imread(filename)
gray= cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)

gray= np.float32(gray)
dst = cv2.cornerHarris(gray,2,3,0.04)

dst = cv2.dilate(dst,None)

img[dst>0.01*dst.max()] = [0,0,255]

cv2.imshow('dst',img)

if cv2.waitKey(0) & 0xff == 27:
cv2.destroyAllWindows()

2.3 亚像素精度的拐角

什么是亚像素,就是将两个像素之间进行细分,将每个像素划分为更小的单元。
有时候,你需要获取最大精度的角。opencv提供了一个函数cv2.cornerSubPix()函数来重新获取亚精度的拐角。首先,我们需要获取Harris拐角,然后,我们将这些拐角的中心传递到函数中,来重新获取它们。在这个函数中,我们需要定义精度来确定什么时候停止。

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
import cv2
import numpy as np

filename = 'chessboard2.jpg'
img = cv2.imread(filename)
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)

# find Harris corners
gray = np.float32(gray)
dst = cv2.cornerHarris(gray,2,3,0.04)
dst = cv2.dilate(dst,None)
ret, dst = cv2.threshold(dst,0.01*dst.max(),255,0)
dst = np.uint8(dst)

# find centroids
ret, labels, stats, centroids = cv2.connectedComponentsWithStats(dst)

# define the criteria to stop and refine the corners
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 100, 0.001)
corners = cv2.cornerSubPix(gray,np.float32(centroids),(5,5),(-1,-1),criteria)

# Now draw them
res = np.hstack((centroids,corners))
res = np.int0(res)
img[res[:,1],res[:,0]]=[0,0,255]
img[res[:,3],res[:,2]] = [0,255,0]

cv2.imwrite('subpixel5.png',img)

这一部分有些不太了解,待定。

3. Shi-Tomasi拐角检测和良好的跟踪功能

这一部分我们需要重新学习一种拐角检测方式:Shi-Tomasi拐角检测

3.1 理论

这部分待定,主要是将Harris拐角检测的公式进行了简化,看看就行。

3.2 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import cv2
from matplotlib import pyplot as plt
#读取图片,然后转换为灰度图
img = cv2.imread('simple.jpg')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)

#通过goodFeaturesToTrack函数通过Shi-Tomasi获取25个最有效的拐角
corners = cv2.goodFeaturesToTrack(gray,25,0.01,10)
corners = np.int0(corners)

for i in corners:
x,y = i.ravel()
cv2.circle(img,(x,y),3,255,-1)

plt.imshow(img),plt.show()

4. SIFT(Scale-Invariant Feature Transform)尺度不变特征变换

5. SURF(Speeded-Up Robust Features)加速鲁棒特征

6. 用于角点检测的快速算法

7. BRIEF(Binary Robust Independent Elementary Features)二进制健壮的独立基本特征

8. ORB(Oriented FAST and Rotated BRIEF)定向快速和旋转BRIEF

9. 特征匹配

10. 特征匹配和单应性查找对象

在Python中调用C代码

0.简介

为什么在Python中调用C代码?
1.提升代码运行速度
2.C语言中的传统类库
3.从内存到文件接口的底层资源访问

有哪些方法?
1.-ctypes
2.SWIG
3.Python/C API

1.CTypes

使用Python中的ctypes模块是最为简单的。ctypes提供了和C兼容的数据类型和函数来加载dll文件,因此在调用时不需要对源文件做任何修改。
下面是例子:

1.1 建立C函数

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>

int add_int(int, int);
float add_float(float, float);

int add_int(int num1, int num2){
return num1 + num2;

}

float add_float(float num1, float num2){
return num1 + num2;
}

1.2 将C语言编译为.so文件(windows下为dll),生成adder.so文件。

1
2
3
4
5
#For Linux
$ gcc -shared -Wl,-soname,adder -o adder.so -fPIC add.c

#For Mac
$ gcc -shared -Wl,-install_name,adder.so -o adder.so -fPIC add.c

1.3 在Python代码中调用C函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from ctypes import *

#load the shared object file
adder = CDLL('./adder.so')

#Find sum of integers
res_int = adder.add_int(4,5)
print "Sum of 4 and 5 = " + str(res_int)

#Find sum of floats
a = c_float(5.5)
b = c_float(4.1)

add_float = adder.add_float
add_float.restype = c_float
print "Sum of 5.5 and 4.1 = ", str(add_float(a, b))

在这个文件中,C语言是自解释的。

在Python文件中,一开始先导入ctypes模块,然后使用CDLL函数来加载我们创建的库文件。这样我们就可以通过变量adder来使用C类库中的函数了。当adder.add_int()被调用时,内部将发起一个对C函数add_int的调用。ctypes接口允许我们在调用C函数时使用原生Python中默认的字符串型和整型。

而对于其他类似布尔型和浮点型这样的类型,必须要使用正确的ctype类型才可以。如向adder.add_float()函数传参时, 我们要先将Python中的十进制值转化为c_float类型,然后才能传送给C函数。这种方法虽然简单,清晰,但是却很受限。例如,并不能在C中对对象进行操作。

2.SWIG(Simplified Wrapper and Interface Generator)

这个方法需要编写额外的接口文件来作为SWIG的入口,一般不使用这个方法,因为比较复杂。但是,当你一个C/C++代码库需要被多种语言调用时,这就很有用了。
例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//example.c
#include <time.h>
double My_variable = 3.0;

int fact(int n) {
if (n <= 1) return 1;
else return n*fact(n-1);

}

int my_mod(int x, int y) {
return (x%y);

}

char *get_time()
{
time_t ltime;
time(&ltime);
return ctime(&ltime);
}

编译:

1
2
3
4
unix % swig -python example.i
unix % gcc -c example.c example_wrap.c \
-I/usr/local/include/python2.1
unix % ld -shared example.o example_wrap.o -o _example.so

输出:

1
2
3
4
5
6
7
>>> import example
>>> example.fact(5)
120
>>> example.my_mod(7,3)
1
>>> example.get_time()
'Sun Feb 11 23:01:07 1996'

3.Python/C API

最广泛使用的方法,不仅简单而且可以在C代码中操作Python对象。
这种方法需要以特定的方式来编写C代码以供Python去调用它。所有的Python对象都被表示为一种叫做PyObject的结构体,并且Python.h头文件中提供了各种操作它的函数。例如,如果PyObject表示为PyListType(列表类型)时,那么我们便可以使用PyList_Size()函数来获取该结构的长度,类似Python中的len(list)函数。大部分对Python原生对象的基础函数和操作在Python.h头文件中都能找到。

Python上数独处理程序

0.简介

之前,我用C语言编写了数独的运算程序,可以直接在我的代码仓库中找到。但是,有一个部分我想优化一下,关于数独的输入,我希望能够直接通过opencv来直接读取一个数独的图片,然后计算出结果。
现在的目标具体是:
输入一个数独题目的图片,图片非常理想干净,图片中仅有数独,而且没有光影等影响,类似于直接在电脑上截图一样。
大概的思路是这样的:
1.读取图片,然后转换为灰度图,便于处理数据
2.对灰度图进行滤波处理,降低图片中的噪声
3.进行阈值化处理,将图片转变为二值图
4.进行轮廓处理,在此之前可以先膨胀或者侵蚀处理一下
5.寻找数独的最外层轮廓,遍历数独轮廓,父节点有81个子节点
6.对最外层轮廓进行仿射变换,从而提取出标准的数独部分
7.遍历轮廓

如果对其中的函数不太了解,可以看之前博客中关于opencv的内容。

Ubuntu上opencv教程4之图像处理

0. 简介

这部分包含了以下几个部分:

1.颜色空间的改变
学习如何在不同的色彩空间来更改图像,此外还要学会如何跟踪视频中的彩色物体。
2.图片阈值处理
使用全局阈值和自适应阈值来将图像转换为二进制图像
3.图片的几何转换
学习旋转,平移等。
4.平滑图像
学习模糊图像,使用自定义内核过滤图像等。
5.形态转换
了解例如侵蚀,膨胀,打开或者关闭等形态的变化。
6.图像渐变
7.Canny边缘检测
8.影像金字塔
9.opencv中的轮廓
10.opencv中的直方图
11.opencv中的图像转换
12.模板匹配
13.霍夫线变换
14.霍夫圆变换
15.分水岭算法的图像分割
16.使用GrabCut进行交互前景提取

1.颜色空间的改变

这个部分需要学习将图片从一个颜色空间转变为另一个,例如BGR和Gray的变换或者BGR和HSV之间变换等。
除此之外,我们将会建立一个应用来抽取视频中的彩色的物体。
将会使用cv2.cvtColor(),cv2.inRange()等等。

1.1 修改颜色空间

opencv中有150多种颜色空间的变换方法。但是,我们仅仅注意最为重要的以及普遍的两种:BGR<->Gray , BGR <-> HSV。

1.1.0 颜色空间

备注:HSV是什么?Hue Saturation Value
RGB,YUV和HSV颜色空间模型
颜色通常用三个独立的属性来描述,三个独立变量综合作用,自然就构成一个空间坐标,这就是颜色空间。但被描述的颜色对象本身是客观的,不同颜色空间只是从不同的角度去衡量同一个对象。颜色空间按照基本机构可以分为两大类:基色颜色空间和色、亮分离颜色空间。前者典型的是RGB,后者包括YUV和HSV等等。

1.1.1 RGB颜色空间

1、计算机色彩显示器和彩色电视机显示色彩的原理一样,都是采用R、G、B相加混色的原理,通过发射出三种不同强度的电子束,使屏幕内侧覆盖的红、绿、蓝磷光材料发光而产生色彩。这种色彩的表示方法称为RGB色彩空间表示。
2、在RGB颜色空间中,任意色光F都可以用R、G、B三色不同分量的相加混合而成:F=r[R]+r[G]+r[B]
RGB色彩空间还可以用一个三维的立方体来描述。当三基色分量都为0(最弱)时混合为黑色光;当三基色都为k(最大,值由存储空间决定)时混合为白色光。
3、RGB色彩空间根据每个分量在计算机中占用的存储字节数分为如下几种类型:
(1)RGB555
RGB555是一种16位的RGB格式,各分量都用5位表示,剩下的一位不用。
高字节 -> 低字节
XRRRRRGGGGGBBBBB
(2)RGB565
RGB565也是一种16位的RGB格式,但是R占用5位,G占用6位,B占用5位。
(3)RGB24
RGB24是一种24位的RGB格式,各分量占用8位,取值范围为0-255。
(4)RGB32
RGB24是一种32位的RGB格式,各分量占用8位,剩下的8位作Alpha通道或者不用。
4、RGB色彩空间采用物理三基色表示,因而物理意义很清楚,适合彩色显象管工作。然而这一体制并不适应人的视觉特点。因而,产生了其它不同的色彩空间表示法。

1.1.2 YUV颜色空间

1、YUV(亦称YCrCb)是被欧洲电视系统所采用的一种颜色编码方法。在现代彩色电视系统中,通常采用三管彩色摄像机或彩色CCD摄影机进行取像,然后把取得的彩色图像信号经分色、分别放大校正后得到RGB,再经过矩阵变换电路得到亮度信号Y和两个色差信号R-Y(即U)、B-Y(即V),最后发送端将亮度和两个色差总共三个信号分别进行编码,用同一信道发送出去。这种色彩的表示方法就是所谓的YUV色彩空间表示。采用YUV色彩空间的重要性是它的亮度信号Y和色度信号U、V是分离的。如果只有Y信号分量而没有U、V信号分量,那么这样表示的图像就是黑白灰度图像。彩色电视采用YUV空间正是为了用亮度信号Y解决彩色电视机与黑白电视机的兼容问题,使黑白电视机也能接收彩色电视信号。
2、YUV主要用于优化彩色视频信号的传输,使其向后相容老式黑白电视。与RGB视频信号传输相比,它最大的优点在于只需占用极少的频宽(RGB要求三个独立的视频信号同时传输)。其中“Y”表示明亮度(Luminance或Luma),也就是灰阶值;而“U”和“V” 表示的则是色度(Chrominance或Chroma),作用是描述影像色彩及饱和度,用于指定像素的颜色。“亮度”是透过RGB输入信号来建立的,方法是将RGB信号的特定部分叠加到一起。“色度”则定义了颜色的两个方面─色调与饱和度,分别用Cr和Cb来表示。其中,Cr反映了RGB输入信号红色部分与RGB信号亮度值之间的差异。而Cb反映的是RGB输入信号蓝色部分与RGB信号亮度值之同的差异。
3、YUV和RGB互相转换的公式如下(RGB取值范围均为0-255)︰
 Y = 0.299R + 0.587G + 0.114B
 U = -0.147R - 0.289G + 0.436B
 V = 0.615R - 0.515G - 0.100B
 R = Y + 1.14V
 G = Y - 0.39U - 0.58V
 B = Y + 2.03U

1.1.3 HSV颜色空间

1、HSV是一种将RGB色彩空间中的点在倒圆锥体中的表示方法。HSV即色相(Hue)、饱和度(Saturation)、明度(Value),又称HSB(B即Brightness)。色相是色彩的基本属性,就是平常说的颜色的名称,如红色、黄色等。饱和度(S)是指色彩的纯度,越高色彩越纯,低则逐渐变灰,取0-100%的数值。明度(V),取0-max(计算机中HSV取值范围和存储的长度有关)。HSV颜色空间可以用一个圆锥空间模型来描述。圆锥的顶点处,V=0,H和S无定义,代表黑色。圆锥的顶面中心处V=max,S=0,H无定义,代表白色。
2、RGB颜色空间中,三种颜色分量的取值与所生成的颜色之间的联系并不直观。而HSV颜色空间,更类似于人类感觉颜色的方式,封装了关于颜色的信息:“这是什么颜色?深浅如何?明暗如何?”

Hue的范围是[0,179],Saturation的范围是[0,255],value的范围是[0,255]。不同的软件使用不同的规模,如果你用opencv配合其他软件,你需要规范化这些变量。

对于颜色变换,我们通常使用cv2.cvtColor(input_image,flag)函数。
flag决定了变换的类型。

对于BGR->Gray的变换,flag = cv2.COLOR_BGR2GRAY
BGR->HSV变换, flag = cv2.COLOR_BGR2HSV

如果,你希望查找其他的flag,可以用下面的方法:

1
2
3
import cv2
flags = [i for i in dir(cv2) if i.startswith('COLOR_')]
print(flags)

1.2 物体跟踪

现在,我们可以将BGR图像转换为HSV图像,我们可以通过这个来提取一个彩色的物体。在HSV中,我们可以更加容易的代表一种颜色,而不是RGB颜色空间。
我们需要提取一个蓝色的物体,下面是实现方式。
提取视频的每一帧
将BGR转换为HSV颜色空间
我们提取HSV图片中的蓝色范围
单独提取蓝色物体,我们可以在图片上做任何想做的事情。

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
import cv2
import numpy as np

cap = cv2.VideoCapture(0)

while(1):

# Take each frame
_, frame = cap.read()

# Convert BGR to HSV
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)

# define range of blue color in HSV
lower_blue = np.array([110,50,50])
upper_blue = np.array([130,255,255])

# Threshold the HSV image to get only blue colors
mask = cv2.inRange(hsv, lower_blue, upper_blue)

# Bitwise-AND mask and original image
res = cv2.bitwise_and(frame,frame, mask= mask)

cv2.imshow('frame',frame)
cv2.imshow('mask',mask)
cv2.imshow('res',res)
k = cv2.waitKey(5) & 0xFF
if k == 27:
break

cv2.destroyAllWindows()

注意,这个里面会有很多噪音,之后我们需要学习如何消除噪音。

这是物体追踪的最简单的方法。如果你学会了轮廓的函数,你可以做很多事情,例如找到物体的中心和追踪物体,只要在摄像头前移动你的手,就可以花图以及其他有趣的东西。

如何找到追踪的HSV数值?
这是非常常见的问题,你可以使用下面的方法,使用BGR来转换为HSV。
注意,opencv是以BGR格式读取数据,我们需要将RGB进行转换。

1
2
3
4
>>> green = np.uint8([[[0,255,0 ]]])
>>> hsv_green = cv2.cvtColor(green,cv2.COLOR_BGR2HSV)
>>> print hsv_green
[[[ 60 255 255]]]

然后你将[H-10,100,100]和[H+10,255,255]分别作为上下界。
除了这个方法,你还可以通过图片编辑软件GIMP或者在线转换器来找到数值,不要忘记配合HSV的范围。

2.图片阈值处理

图片的阈值处理之前我们已经遇到过,我们还需要有自适应阈值处理。

2.1 简单阈值

直接看代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import cv2
import numpy as np
from matplotlib import pyplot as plt

img = cv2.imread('gradient.png',0)
ret,thresh1 = cv2.threshold(img,127,255,cv2.THRESH_BINARY)
ret,thresh2 = cv2.threshold(img,127,255,cv2.THRESH_BINARY_INV)
ret,thresh3 = cv2.threshold(img,127,255,cv2.THRESH_TRUNC)
ret,thresh4 = cv2.threshold(img,127,255,cv2.THRESH_TOZERO)
ret,thresh5 = cv2.threshold(img,127,255,cv2.THRESH_TOZERO_INV)

titles = ['Original Image','BINARY','BINARY_INV','TRUNC','TOZERO','TOZERO_INV']
images = [img, thresh1, thresh2, thresh3, thresh4, thresh5]

for i in xrange(6):
plt.subplot(2,3,i+1),plt.imshow(images[i],'gray')
plt.title(titles[i])
plt.xticks([]),plt.yticks([])

plt.show()

上面使用了matplotlib绘制函数,可以复习一下。

2.2 自适应阈值

上面是我们使用了一个全局变量作为阈值,但是在有些条件下,图片有不同的光照条件,所以,在这种情况下,我们使用自适应的阈值。在这个方法中,我们需要计算图片中某一区域中的阈值。这样,我们在不同的区域中有不同的阈值,从而得到更好的图片结果。

自适应阈值函数中有三个输入变量和一个输出变量:
1.自适应规则:它决定了阈值的计算方法
cv2.ADAPTIVE_THRESH_MEAN_C
阈值设定为附近区域的中间值
cv2.ADAPTIVE_THRESH_GAUSSIAN_C
阈值取决于高斯窗口附近区域值的加权相加。大概是这么翻译的。又见高斯。。。
2.区块大小:决定了附近区域的大小
3.常数:一个从平均值或者加权平均值中减去的常数。

下面是两个阈值的比较

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import cv2
import numpy as np
from matplotlib import pyplot as plt

img = cv2.imread('dave.jpg',0)
img = cv2.medianBlur(img,5)

ret,th1 = cv2.threshold(img,127,255,cv2.THRESH_BINARY)
th2 = cv2.adaptiveThreshold(img,255,cv2.ADAPTIVE_THRESH_MEAN_C,\
cv2.THRESH_BINARY,11,2)
th3 = cv2.adaptiveThreshold(img,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,\
cv2.THRESH_BINARY,11,2)

titles = ['Original Image', 'Global Thresholding (v = 127)',
'Adaptive Mean Thresholding', 'Adaptive Gaussian Thresholding']
images = [img, th1, th2, th3]

for i in xrange(4):
plt.subplot(2,2,i+1),plt.imshow(images[i],'gray')
plt.title(titles[i])
plt.xticks([]),plt.yticks([])
plt.show()

上面的程序中有一个中值滤波来模糊化图片,能够有效地抑制噪声,从而消除孤立的噪声点。
cv2.medianBlur(src,ksize[,dst])->dst
变量
src:输入1,3,4通道图片,当ksize是3或者5,图片的深度应该是CV_8U,CV_16U或者是CV_32F,对于更加大的孔径,则只能是CV_8U
dst:和src一样大小和类型的目标数组
ksize:孔径大小,必须是奇数,而且大于1。

这个函数通过中值滤波,在ksize×ksize的孔径中过滤,每个频道可以分别运算。

下面是最重要的自适应函数部分,

1
cv2.adaptiveThreshold(src, maxValue, adaptiveMethod, thresholdType, blockSize, C[, dst]) → dst

其中,
src:8位单通道图片
dst:有和原图片相同大小和类型的目标图片
maxValue:非0值,和之前的阈值函数一样
adaptiveMethod:自适应阈值的算法,类型有:
ADAPTIVE_THRESH_MEAN_C和ADAPTIVE_THRESH_GAUSSIAN_C
thresholdType:阈值类型有THRESH_BINARY或者THRESH_BINARY_INV
blockSize:像素区域的大小,3,5,7等等。
C:常数,用来将平均值和加权平均值减去的数字。

2.3 大津二值化算法

Otsu’s Binarization说实话,我不明白这是个人名还是什么的。
介绍一下,这是最大类间方差法,是一种自动确定二值化阈值的算法。
推导过程暂放,说一说它的结论:
Sb2=w0w1(M0-M1)2
二值化阈值t将完整的区间分成两个类,Sb2类间方差最大时,t就是二值化阈值。
这个公式的变量:
Sb2:类间方差
w0,w1:被阈值t分开的两个类中的像素数占总像素数的比例
M0,M1:
两个类的像素值的平均值

里面第二个变量是retVal,当我们使用大津二值化算法就用到它。
在全局阈值的情况下,我们使用一个任意值作为阈值。那么我们怎么看这个数值是好是坏呢?试错。考虑一个双峰图像,就是直方图有两个峰值。
对于那种图片,我们可以大概选取这两个峰值之间的数值来作为阈值。那么,大津二值化方法就是自动选取阈值,对于其他图片没有办法计算。

因此,cv2.threshold()函数使用了,传入了一个其他的标志,cv2.THRESH_OTSU,将0赋值给阈值。然后,这个算法就会找到合适的阈值并且将它作为第二个返回值,retVal。如果不使用大津二值化方法,retVal就会是你所使用的阈值。

例如,将一个充满噪声的图片输入,分别进行下列操作:
1.使用一个全局阈值127
2.使用大津二值化算法
3.5×5的高斯滤波,然后使用大津二值化算法。

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
import cv2
import numpy as np
from matplotlib import pyplot as plt

img = cv2.imread('noisy2.png',0)

# global thresholding
ret1,th1 = cv2.threshold(img,127,255,cv2.THRESH_BINARY)

# Otsu's thresholding
ret2,th2 = cv2.threshold(img,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)

# Otsu's thresholding after Gaussian filtering
blur = cv2.GaussianBlur(img,(5,5),0)
ret3,th3 = cv2.threshold(blur,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)

# plot all the images and their histograms
images = [img, 0, th1,
img, 0, th2,
blur, 0, th3]
titles = ['Original Noisy Image','Histogram','Global Thresholding (v=127)',
'Original Noisy Image','Histogram',"Otsu's Thresholding",
'Gaussian filtered Image','Histogram',"Otsu's Thresholding"]

for i in xrange(3):
plt.subplot(3,3,i*3+1),plt.imshow(images[i*3],'gray')
plt.title(titles[i*3]), plt.xticks([]), plt.yticks([])
plt.subplot(3,3,i*3+2),plt.hist(images[i*3].ravel(),256)
plt.title(titles[i*3+1]), plt.xticks([]), plt.yticks([])
plt.subplot(3,3,i*3+3),plt.imshow(images[i*3+2],'gray')
plt.title(titles[i*3+2]), plt.xticks([]), plt.yticks([])
plt.show()

这里只有一些函数需要补充。

1
2
plt.hist():用来表示直方图
等等,省略了相关的内容

顺便,大津二值方法可以直接用下面的代码实现:

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
img = cv2.imread('noisy2.png',0)
blur = cv2.GaussianBlur(img,(5,5),0)

# find normalized_histogram, and its cumulative distribution function
hist = cv2.calcHist([blur],[0],None,[256],[0,256])
hist_norm = hist.ravel()/hist.max()
Q = hist_norm.cumsum()

bins = np.arange(256)

fn_min = np.inf
thresh = -1

for i in xrange(1,256):
p1,p2 = np.hsplit(hist_norm,[i]) # probabilities
q1,q2 = Q[i],Q[255]-Q[i] # cum sum of classes
b1,b2 = np.hsplit(bins,[i]) # weights

# finding means and variances
m1,m2 = np.sum(p1*b1)/q1, np.sum(p2*b2)/q2
v1,v2 = np.sum(((b1-m1)**2)*p1)/q1,np.sum(((b2-m2)**2)*p2)/q2

# calculates the minimization function
fn = v1*q1 + v2*q2
if fn < fn_min:
fn_min = fn
thresh = i

# find otsu's threshold value with OpenCV function
ret, otsu = cv2.threshold(blur,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
print thresh,ret

3.图片的几何转换

学习平移,旋转,仿射变换等。
使用下面的函数cv2.getPerspectiveTransform

3.0 转换

opencv有两个变换函数cv2.warpAffine和cv2.warpPerspective。
通过这两个函数可以使用任意种类的变换:
cv2.warpAffine采用2×3变换矩阵,
cv2.warpPerspective将3×3个变换矩阵作为输入

3.1 比例缩放

缩放修改图片的大小,opencv包含一个cv2.resize()函数。可以手动指定图像的大小,也可以指定缩放系数。使用不同的插值方法。推荐的插值方法是cv2.INTER_AREA收缩和cv2.INTER_CUBIC(慢)和cv2.INTER_LINEAR(快速)
默认使用cv2.INTER_LINEAR插值方法,可以使用下面的方法来实现修改图片大小。

1
2
3
4
5
6
7
8
9
10
11
import cv2
import numpy as np

img = cv2.imread('messi5.jpg')

res = cv2.resize(img,None,fx=2, fy=2, interpolation = cv2.INTER_CUBIC)

#OR

height, width = img.shape[:2]
res = cv2.resize(img,(2*width, 2*height), interpolation = cv2.INTER_CUBIC)

cv2.resize(src, dsize[, dst[, fx[, fy[, interpolation]]]]) → dst
变量有:
src :输入图片
dst :输出图片
dsize:输出图片的大小,如果为0,则通过下面的公式计算:
dsize = size(round(fxsrc.cols),round(fysrc.rows))
fx,fy:分别是缩放系数
interpolation:插值方法
INTER_NEAREST:最近临插值法
INTER_LINEAR:双线性插值法,默认
INTER_AREA: 使用像素区域重新采样。它可能是图像抽取的首选方法,因为它提供了无莫尔的结果。但是,当图像被缩放时,它类似于INTER_NEAREST方法。
INTER_CUBIC:4×4临域像素点上的bixubic插值(双三次插值)
INTER_LANCZOS4:8x8临域像素点上的Lanczos插值

3.2 平移

平移是修改物体的位置,如果知道平移的方向(tx,ty),可以建立一个转换矩阵。

1 0 tx
0 1 ty

我们可以建立np.float32类新的数组,然后将变量传递到cv2.warpAffine
例如:

1
2
3
4
5
6
7
8
9
10
11
import numpy as np

img = cv2.imread('messi5.jpg',0)
rows,cols = img.shape

M = np.float32([[1,0,100],[0,1,50]])
dst = cv2.warpAffine(img,M,(cols,rows))

cv2.imshow('img',dst)
cv2.waitKey(0)
cv2.destroyAllWindows()

这样,图片就进行了平移的操作。

3.3 旋转

旋转的角度x,可以变换为变换矩阵M:

cosx -sinx
sinx cosx

但是,opencv提供了以合适的旋转中心缩放旋转的函数,所以你可以旋转你所需要的角度和位置,可以将转换矩阵修改为

a b (1-a)center.x-bcenter.y
-b a b*center.x+(1-a)*center.y

其中:

a=scalecosx
b=scale
sinx

opencv提供了cv2.getRotationMatrix2D函数,用来获取上面的转换矩阵。
下面的例子中,以图片的中心逆时针旋转90度。

1
2
3
4
5
img = cv2.imread('messi5.jpg',0)
rows,cols = img.shape

M = cv2.getRotationMatrix2D((cols/2,rows/2),90,1)
dst = cv2.warpAffine(img,M,(cols,rows))

其中:
cv2.getRotationMatrix2D()的变量有
旋转中心,旋转角度,放大或缩小系数。

3.4 仿射变换

在仿射变换中,所有原图片中的平行线将会在输出图片中保持平行。
为了找到变换矩阵,我们需要原图片中的三个点,和输出图片中对应的位置。然后,cv2.getAffineTransform()会建立一个2×3的矩阵作为cv2.warpAffine()的输入变量。

下面的例子中,选取的点用绿色来标记。

1
2
3
4
5
6
7
8
9
10
11
12
13
img = cv2.imread('drawing.png')
rows,cols,ch = img.shape

pts1 = np.float32([[50,50],[200,50],[50,200]])
pts2 = np.float32([[10,100],[200,50],[100,250]])

M = cv2.getAffineTransform(pts1,pts2)

dst = cv2.warpAffine(img,M,(cols,rows))

plt.subplot(121),plt.imshow(img),plt.title('Input')
plt.subplot(122),plt.imshow(dst),plt.title('Output')
plt.show()

3.5 透视变换

对于透视变换,我们需要一个3×3变换矩阵。在变换前后,直线始终笔直。
为了找到这个变换矩阵,我们需要输入图片的四个点并且输出图片中相对应的点。在这四个点中,它们中的三个不能共线。然后通过cv2.getPerspectiveTransform函数来找到变换矩阵,然后在cv2.warpPerspective中使用3×3变换矩阵。
可以看到下面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
img = cv2.imread('sudokusmall.png')
rows,cols,ch = img.shape

pts1 = np.float32([[56,65],[368,52],[28,387],[389,390]])
pts2 = np.float32([[0,0],[300,0],[0,300],[300,300]])

M = cv2.getPerspectiveTransform(pts1,pts2)

dst = cv2.warpPerspective(img,M,(300,300))

plt.subplot(121),plt.imshow(img),plt.title('Input')
plt.subplot(122),plt.imshow(dst),plt.title('Output')
plt.show()

4.平滑图像

使用各种低通滤波器来平滑图像。
对图像使用自定义过滤器(二维卷积)

4.1 二维卷积(图片过滤)

对于一维的信号,图片可以通过低通滤波器来滤波(LPF),高通滤波器(HPF)等等。一个LPF可以用于消除噪音,或者模糊图像。一个HPF可以找到图片中的边缘。

opencv提供了cv2.filter2D()函数,用图像来卷积内核。下面的例子中,我们将会对图像进行平均滤波,一个5×5的平均滤波器内核K的定义是。

25K =

1 1 1 1 1
1 1 1 1 1
1 1 1 1 1
1 1 1 1 1
1 1 1 1 1

使用上面的滤波器内核进行过滤的结果是,对于每个像素,5×5窗口以该像素所为中心,将该窗口内的所有像素相加,然后将结果除以25。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import cv2
import numpy as np
from matplotlib import pyplot as plt

img = cv2.imread('opencv_logo.png')

kernel = np.ones((5,5),np.float32)/25
dst = cv2.filter2D(img,-1,kernel)

plt.subplot(121),plt.imshow(img),plt.title('Original')
plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(dst),plt.title('Averaging')
plt.xticks([]), plt.yticks([])
plt.show()

其中:
cv2.filter2D(src, ddepth, kernel[, dst[, anchor[, delta[, borderType]]]]) → dst
变量:
ddepth:目标图片的深度,如果为负数,它将会和原图片的深度相同。
其他的变量可以缺省,暂时待定。

4.2 图片模糊(平滑图像)

图像模糊是通过卷积图像与低通滤波核来实现的。它对消除噪音很有用。
它实际上会从图像中移除高频内容(例如:噪声、边缘),从而在应用此 滤波器时导致边缘模糊。(当然,有些模糊技术不会模糊边缘)。OpenCV主要提供四种模糊技术。

4.2.1 均值

这是通过卷积图像与一个规范化的盒子过滤器。它只需要取内核区域下所有像素的平均值,并用该平均值替换中心元素。这是通过函数blur或boxFilter完成。我们应该指定内核的宽度和高度。一个5x5规格化的盒式过滤器如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
import cv2
import numpy as np
from matplotlib import pyplot as plt

img = cv2.imread('opencv_logo.png')

blur = cv2.blur(img,(5,5))

plt.subplot(121),plt.imshow(img),plt.title('Original')
plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(blur),plt.title('Blurred')
plt.xticks([]), plt.yticks([])
plt.show()

注意,如果不想使用规范化的框过滤器,请使用cv2.boxFilter并将参数normalize=False传递给函数。

4.2.2 高斯滤波

在这种方法中,使用高斯核替代了由相同的系数组成的盒子过滤器。
使用了cv2.GaussianBlur函数。
我们应该确定核心的高度和宽度,他们必须是正奇数。
同时,我们需要设定X和Y方向的标准偏差,分别为sigmaX和sigmaY。
如果只设定了sigmaX,那么sigmaY的数值相同。如果都为0,那么通过核心的大小来计算标准偏差。
高斯滤波在图片中消除高斯噪声中有很有效。

可以通过cv2.getGaussianKernel函数来获取高斯核心。
上面的代码可以修改为

1
blur = cv2.GaussianBlur(img,(5,5),0)

4.2.3 中值滤波

cv2.medianBlur函数可以计算出核心区域中像素点的中间值,并且将中间的像素点的数值修改为中间值。这个方法可以高效消除椒盐噪声。
在高斯和平均滤波中,过滤之后的数值可能在原图像中并不存在。
然而,中值滤波中,因为中心元素总是被图像中的某个像素值代替,这有效地降低了噪音。内核大小必须是正奇数。

1
median = cv2.medianBlur(img,5)

4.2.4 双边滤波

正如之前注意到的,前面的滤波器都会将边缘模糊。双边滤波器cv2.bilateralFilter会在保留边缘的情况下,非常有效地消除噪声。
但是与其他滤波器相比,速度会更慢。
原理没有看懂。。尴尬。直接看需要的参数。

1
blur = cv2.bilateralFilter(img,9,75,75)

cv2.bilateralFilter()中变量有:
src:原图像
dst:目标图像
d:像素临域的直径,如果小于0,则通过sigmaSpace计算
sigmaColor:在颜色空间中过滤sigma,参数值越大,像素临域中更多的颜色将会混合在一起。从而,产生更大的混合颜色区域。大概是这个意思。
sigmaSpace:
在坐标空间中过滤sigma。参数值越大,意味着更远的像素将相互影响,只要它们的颜色足够接近(参见sigmaColor)。当d>0时,它指定邻域大小,而不考虑sigmaSpace。否则,d与sigmaSpace成比例。

5.形态转换

我们将会学习不同的形态学操作,例如:侵蚀,扩张,打开,关闭等。
我们将会使用以下的函数:
cv2.erode()
cv2.dilate()
cv2.morphologyEx()

5.0 理论

形态转化是基于图片形状的一些简单运算。它通常在二进制图像中运行。它需要两个输入,一个是原始图像,另一个是决定操作性质的结构元素或者内核。两个基本的形态学操作是侵蚀和扩张。然后它的各种形式例如:开,关,梯度等也发挥作用。

5.1 侵蚀

侵蚀的基本概念就像土壤侵蚀,它会侵蚀前景对象的边界,总是尽量保持前景为白色。它有什么作用呢?内核在图像中滑动(例如,二维卷积),原始图像中的像素(取值为0或1)在内核都为1的情况下,为1。1为白色,0为黑色。其他则被侵蚀,为0。

1
2
3
4
5
6
import cv2
import numpy as np

img = cv2.imread('j.png',0)
kernel = np.ones((5,5),np.uint8)
erosion = cv2.erode(img,kernel,iterations = 1)

5.2 扩张

和侵蚀相反,当内核中至少一个像素为1时,那么那个像素为1。所以,它增加了白色区域的大小或者前景物体增加。

正常情况下,噪声消除的情况下,侵蚀之后会扩张。因为,侵蚀消除了白噪音,但是也会缩小了我们的目标。所以我们把它放大。既然噪声消失,但是我们的区域缩小了。它在分割图像上也有用处。

1
dilation = cv2.dilate(img,kernel,iterations = 1)

5.3 打开

打开是侵蚀之后扩张的别称。对于消除噪声很有效。说实话,在看这部分的时候哪个在前哪个在后,傻傻分不清楚。

1
opening = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)

5.4 关闭

关闭是扩张之后侵蚀的别称。可以消除前景物体的空洞。

1
closing = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)

5.5 形态梯度

这是扩张和侵蚀之间的差。表现为物体的轮廓。

1
gradient = cv2.morphologyEx(img, cv2.MORPH_GRADIENT, kernel)

5.6 Top Hat(高帽?)

原始图片-打开运算(先腐蚀后扩张)后的图片。

1
tophat = cv2.morphologyEx(img, cv2.MORPH_TOPHAT, kernel)

5.7 Black Hat(黑帽?)

关闭运算(先扩张后腐蚀)后的图片-原始图片。

1
blackhat = cv2.morphologyEx(img, cv2.MORPH_BLACKHAT, kernel)

5.8 结构元素

我们通常使用Numpy来建立构造元素在之前的例子里面,是矩形的。但是在一些情况下,有时候我们需要椭圆或者圆形的核心。
因此有一个函数getStructuringElement。你可以将形状和大小传递进去,然后得到需要的核心。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# Rectangular Kernel
>>> cv2.getStructuringElement(cv2.MORPH_RECT,(5,5))
array([[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1]], dtype=uint8)

# Elliptical Kernel
>>> cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(5,5))
array([[0, 0, 1, 0, 0],
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
[0, 0, 1, 0, 0]], dtype=uint8)

# Cross-shaped Kernel
>>> cv2.getStructuringElement(cv2.MORPH_CROSS,(5,5))
array([[0, 0, 1, 0, 0],
[0, 0, 1, 0, 0],
[1, 1, 1, 1, 1],
[0, 0, 1, 0, 0],
[0, 0, 1, 0, 0]], dtype=uint8)

6.图像渐变

这部分我们需要学习。
找到图片渐变,边缘等。
使用下列的函数:
cv2.Sobel()
cv2.Scharr()
cv2.Laplacian()

6.0 理论

opencv提供了三种类型的梯度滤波器或者高通滤波器:Sobel,Scharr和Laplacian。

6.0.1 Sobel-Scharr导数

来了,来了。Sobel算子是一种高斯平滑加微分的联合算子,因此对噪声的抵抗能力更强。可以指定要获取的垂直或水平导数的方向(分别由参数yorder和xorder指定)。还可以通过参数ksize指定内核的大小。如果ksize=-1,则使用3x3Scharr滤波器,其结果比3x3Sobel滤波器更好。

6.0.2 拉普拉斯导数

待定。。。

6.1 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import cv2
import numpy as np
from matplotlib import pyplot as plt

img = cv2.imread('dave.jpg',0)

laplacian = cv2.Laplacian(img,cv2.CV_64F)
sobelx = cv2.Sobel(img,cv2.CV_64F,1,0,ksize=5)
sobely = cv2.Sobel(img,cv2.CV_64F,0,1,ksize=5)

plt.subplot(2,2,1),plt.imshow(img,cmap = 'gray')
plt.title('Original'), plt.xticks([]), plt.yticks([])
plt.subplot(2,2,2),plt.imshow(laplacian,cmap = 'gray')
plt.title('Laplacian'), plt.xticks([]), plt.yticks([])
plt.subplot(2,2,3),plt.imshow(sobelx,cmap = 'gray')
plt.title('Sobel X'), plt.xticks([]), plt.yticks([])
plt.subplot(2,2,4),plt.imshow(sobely,cmap = 'gray')
plt.title('Sobel Y'), plt.xticks([]), plt.yticks([])

plt.show()

6.2 一件重要的事情

在上一个示例中,输出数据类型是cv2.CV_8U或np.uint8。
但有一个小问题。黑白过渡为正斜率(有正值),黑白过渡为负斜率(有负值)。因此,当您将数据转换为np.uint8时,所有负斜率都设为零。
简单地说,你错过了这一点。
如果要检测两条边,更好的选择是将输出数据类型保持为一些更高的形式,如cv2.CV_16S、cv2.CV_64F等。取其绝对值,然后转换回cv2.CV_8U。下面的代码演示了水平Sobel筛选器的此过程和结果差异。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import cv2
import numpy as np
from matplotlib import pyplot as plt

img = cv2.imread('box.png',0)

# Output dtype = cv2.CV_8U
sobelx8u = cv2.Sobel(img,cv2.CV_8U,1,0,ksize=5)

# Output dtype = cv2.CV_64F. Then take its absolute and convert to cv2.CV_8U
sobelx64f = cv2.Sobel(img,cv2.CV_64F,1,0,ksize=5)
abs_sobel64f = np.absolute(sobelx64f)
sobel_8u = np.uint8(abs_sobel64f)

plt.subplot(1,3,1),plt.imshow(img,cmap = 'gray')
plt.title('Original'), plt.xticks([]), plt.yticks([])
plt.subplot(1,3,2),plt.imshow(sobelx8u,cmap = 'gray')
plt.title('Sobel CV_8U'), plt.xticks([]), plt.yticks([])
plt.subplot(1,3,3),plt.imshow(sobel_8u,cmap = 'gray')
plt.title('Sobel abs(CV_64F)'), plt.xticks([]), plt.yticks([])

plt.show()

7.Canny边缘检测

这部分内容主要有:
Canny边缘检测的概念
opencv中的cv2.Canny函数

7.0 理论

7.0.1 降噪

7.0.2 找到图片的强度梯度

7.0.3 非最大抑制

7.0.4 滞后阈值

7.1 opencv中的Canny边缘检测

1
2
3
4
5
6
7
8
9
10
11
12
13
import cv2
import numpy as np
from matplotlib import pyplot as plt

img = cv2.imread('messi5.jpg',0)
edges = cv2.Canny(img,100,200)

plt.subplot(121),plt.imshow(img,cmap = 'gray')
plt.title('Original Image'), plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(edges,cmap = 'gray')
plt.title('Edge Image'), plt.xticks([]), plt.yticks([])

plt.show()

8.影像金字塔

9.opencv中的轮廓

这个部分感觉挺复杂的,包含:
轮廓入门:
学习查找和绘制轮廓
轮廓特征:
学习查找轮廓的不同特征,例如面积,周长,边界矩形等。
轮廓属性:
学习查找轮廓的不同属性,例如实体度,平均强度等。
轮廓更多功能:
学习查找凸度缺陷,pointPolygonTest,匹配不同的形状等。
轮廓层次:
了解轮廓层次结构

9.1 轮廓入门:

学习查找和绘制轮廓
将会使用以下的函数:
cv2.findContours()
cv2.drawContours()

9.1.1 什么是轮廓

轮廓可以解释为所有沿边界的连续点的曲线,具有相同的颜色或者强度。轮廓线是形状分析,目标检测和识别的工具。

注意:
为了获得更好的精度,请使用二进制图像。因此,在寻找轮廓前,应该用阈值或者Canny边缘检测。
findContours函数会修改原始图片。所以如果你希望在寻找轮廓后使用原始图像,提前将它存储在其他变量中。
在opencv中,寻找轮廓就像在黑色的背景中寻找白色的物体。所以记住,目标物体需要是白色的,而且背景是黑色的。

那么如何在一个二进制图片中找到轮廓呢?

1
2
3
4
5
6
7
import numpy as np
import cv2

im = cv2.imread('test.jpg')
imgray = cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)
ret,thresh = cv2.threshold(imgray,127,255,0)
image, contours, hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)

看到上面的代码,我想到一个问题:
直接读取彩色图片同时转换为灰度图和先读取彩色图片然后转换为灰度图这两个方法有什么区别。。待定。

在findContours中有三个变量:
第一个是原始图片
第二个是轮廓提取模式
第三个是轮廓近似法
输出变量分别是:图片,轮廓和层次

代码中,contours是Python中图片里所有轮廓的list。
每一个独立的轮廓是物体边界点(x,y)坐标的Numpy数组。

之后,我们将会详细讨论第二和第三个参数以及层级的问题。

9.1.2 如何绘制轮廓

使用drawContours函数来绘制函数。只要你有它的边界点就可以绘制任意形状。
第一个参数是原始图像
第二个参数是作为Pyhon列表传递的轮廓
第三个参数是轮廓索引,在绘制单个轮廓时有用。绘制所有轮廓时需要参数为-1
剩下的分别为颜色,厚度等。

在一个图片中绘制所有轮廓。

1
img = cv2.drawContours(img, contours, -1, (0,255,0), 3)

绘制单独的轮廓

1
img = cv2.drawContours(img, contours, 3, (0,255,0), 3)

大多数时间,我们都使用下面的方法。

1
2
cnt = contours[4]
img = cv2.drawContours(img, [cnt], 0, (0,255,0), 3)

下面的更加常见。

9.1.3 轮廓近似方法

这是findContours函数的第三个参数。
上面,我们说轮廓具有相同强度的形状的边界。他存储形状边界的(x,y)坐标,但是不能存储所有的坐标。该轮廓由轮廓近似方法指定。
如果通过CHAIN_APPROX_NONE,则存储所有边界点。但实际上我们需要所有的点吗?例如,你发现了一条直线的轮廓。你需要这条线上的所有点来表示那条线吗?不,我们只需要那条线的两个端点。这就是CHAIN_APPROX_SIMPLE所做的。它删除所有冗余点并压缩轮廓,从而节省内存。
演示这种技术。只需在轮廓数组中的所有坐标上画一个边界轮廓(用蓝色绘制)。第一个方案我用CHAIN_APPROX_NONE得到的点(734个点),第二个方案用CHAIN_APPROX_SIMPLE得到的点(只有4个点)。看,它节省了多少内存!!!

9.2轮廓特征:

学习查找轮廓的不同特征,例如面积,周长,边界矩形等。

9.3轮廓属性:

学习查找轮廓的不同属性,例如实体度,平均强度等。

9.4轮廓更多功能:

学习查找凸度缺陷,pointPolygonTest,匹配不同的形状等。

9.5轮廓层次:

了解轮廓层次结构

10.opencv中的直方图

11.opencv中的图像转换

12.模板匹配

13.霍夫线变换

14.霍夫圆变换

15.分水岭算法的图像分割

16.使用GrabCut进行交互前景提取