python绘制一份完美的中国地图

本文章小编将带你学会使用python绘制一份完美的中国地图~

昨日,突地被一大早的微博热搜 #自然资源部核查处理问题中国地图# 刷屏,恍惚中看到了近日在追的《亲爱的,热爱的》。小编还沉浸在酣甜的剧情里,今的又有新热搜了!!

作为一家资(jia)深(mao)剧粉,带着欣(ba)喜(gua)的心情浏览的热搜,嗯???好像哪里不对……

第39集中存在的“问题地图”引发广泛关注与讨论。

该剧中使用的地图存在错误表示阿克赛钦和我国藏南地区国界线、我国台湾和海南岛底色与大陆不一致、漏绘我国南海诸岛和南海断续线、克什米尔地区不符合国家有关规定等问题。

不少网友表示,既然是公开播放的剧就该好好审核,制作方与审核方都应该在国家主权上谨慎且坚持一个完整国家的原则。

绝非小题大做!国家版图与国歌、国旗一样,是一个主权国家的重要标志,体现的事一个国家的完整政治主张,绝不仅是一张图画而已。错误的国家地图不仅损害了国家利益,更可怕的是它会向公众传播错误的知识,并弱化公众心中国家完整的这一认知。

经过一番倒腾,小编决定亲手为大家奉上一份使用python绘制的完美的中国地图

下面是又一种我国地图绘制的正确方式!!


进行这个实验你需要有以下的环境:

  • python 3.6 以上

首先安装pyecharts, 默认安装是最新版本的,最新版本的pyecharts需要python3.6以上的版本,在安装好python并将pip加入到环境变量之后:

windows系统 打开cmd输入:

pip install pyecharts

macOS系统 打开terminal输入:

pip install pyecharts

出现 Successfully installed 后就表明 pyecharts 安装完毕。下面我们将教程分成三个部分:

  • 1. 利用pyecharts使用Python画中国地图
  • 2. 在中国地图上标记出 《亲爱的 热爱的》 剧中的地图所缺失的部分
  • 3. 将地图保存成图片
1. 首先是第一步,我们可以试着使用 pyecharts 来绘制一个简单的中国地图

在任意新建的一个文件夹下(注意路径不要有中文),创建 map.py 将以下代码写入:

from pyecharts.charts import Geo

# ->Geo 是函数注解,表示该函数返回值为Geo对象
def geo_effectscatter() -> Geo:
    # 以下为链式调用方法声明对象
    c = (
        Geo()

        # 添加底部地图
        .add_schema(maptype="china")
    )
    return c

# 生成对象
c = geo_effectscatter()

# 渲染地图
c.render()

运行

windows系统:打开cmd,cd 进入当前文件夹,输入下面的指令

python map.py

macOS系统:打开terminal,cd进入当前文件夹,输入上述指令

运行完毕后,你会看见当前目录下有一个render.html生成,使用浏览器打开这个文件便能看见我们的地图啦,代码中有几个值得注意的地方,第一个是:

def geo_effectscatter() -> Geo:

与普通的函数声明不同,其后面带了一个函数注解(->Geo)表明该函数的返回值是Geo对象。

第二个是:

c = (
        Geo()
        .add_schema(maptype="china")
        # 添加底部地图
    )

这个是Python的链式调用,其效果等同于

c = Geo()
c.add_schema(maptype="china")

生成的中国地图如下:

2. 第二步, 在中国地图上标记出 《亲爱的 热爱的》 剧中的地图所缺失的部分

在当前目录下新建文件 map_mark.py

from pyecharts import options as opts
from pyecharts.charts import Geo
from pyecharts.globals import ChartType
def geo_effectscatter() -> Geo:

    # 初始化地图参数 page_title: 页面标题, theme: 画布主题(主题列表可见Echarts官网)
    InitOpts = opts.InitOpts(page_title="中国地图", theme="light")
    c = (
        # 声明对象时将初始化参数
        Geo(InitOpts)
        # 添加底部地图
        .add_schema(
            maptype="china")

        # 增加区域点(阿克赛钦和藏南地区)
        .add_coordinate(
            name='阿克赛钦',
            longitude=78.928266,
            latitude=35.115117
        )
        .add_coordinate(
            name='藏南地区',
            longitude=93.128902,
            latitude=27.616436
        )

        # 将剧中地图缺少的地方标记出来
        .add(
            "《亲爱的 热爱的》剧中地图缺少的部分",
            [['海南',100], ['台湾', 100], ['阿克赛钦', 100], ['藏南地区', 100]],
            type_=ChartType.EFFECT_SCATTER,
        )
        # 显示出这个点的标签(formatter={b} 表示显示地区名称,详细可见:
        #  https://pyecharts.org/#/zh-cn/series_options )
        .set_series_opts(label_opts=opts.LabelOpts(is_show=True, formatter="{b}", font_size=14))

        # 设置地图名称,即左上角
        .set_global_opts(title_opts=opts.TitleOpts(title="中国地图"))
    )
    return c    
# 生成对象
c = geo_effectscatter()

# 渲染地图
c.render()

运行同第一步一样,改个文件名即可,记得不要在路径中包含中文。运行完毕后会在当前目录下生成render.html,使用浏览器打开即可看见地图

效果如下:

3. 第三步,将地图保存成图片

pyecharts 中生成图片有两种方法,一种是selenium方法,还有一种是用phantomjs. 本教程推荐使用 phantomjs.

首先,我们需要安装 snapshot-phantomjs. 同安装echarts一样,打开cmd (Windows) 或者terminal (macOS) 输入以下指令:

pip install snapshot-phantomjs

然后,我们需要前往phantomjs官网下载phantomjs:2.1.1版本下载链接

phantomjs的安装在这里以Windows为例进行讲解:

解压下载的压缩包后,得到phantomjs-2.1.1-windows,里面bin文件夹里的phantomjs.exe就是我们需要的程序,我们需要将其加入到环境变量中。

windows 10在左下角搜索环境变量即可。

windows7则需要如下图所示打开环境变量(windows10 也可以这样打开)

向系统变量的path的后面,加入我们 phantomjs.exe 的路径,比如我的是加入:

C:\Users\Ckend\Downloads\phantomjs-2.1.1-windows\phantomjs-2.1.1-windows\bin

如果你是windows 7系统,记得用;与前一条path隔开。

;C:\Users\Ckend\Downloads\phantomjs-2.1.1-windows\phantomjs-2.1.1-windows\bin

接下来就可以生成图片了,在代码的首部引入我们刚刚的两个包

from pyecharts.render import make_snapshot
from snapshot_phantomjs import snapshot

最后我们调用生成图片的函数即可

# 生成图片
make_snapshot(snapshot, c.render(), "map.png")

完整代码如下:

from pyecharts.render import make_snapshot
from snapshot_phantomjs import snapshot

from pyecharts import options as opts
from pyecharts.charts import Geo
from pyecharts.globals import ChartType
def geo_effectscatter() -> Geo:

    # 初始化地图参数 page_title: 页面标题, theme: 画布主题(主题列表可见Echarts官网)
    InitOpts = opts.InitOpts(page_title="中国地图", theme="light")
    c = (
        # 声明对象时将初始化参数
        Geo(InitOpts)
        # 添加底部地图
        .add_schema(
            maptype="china")

        # 增加区域点(阿克赛钦和藏南地区)
        .add_coordinate(
            name='阿克赛钦',
            longitude=78.928266,
            latitude=35.115117
        )
        .add_coordinate(
            name='藏南地区',
            longitude=93.128902,
            latitude=27.616436
        )

        # 将剧中地图缺少的地方标记出来
        .add(
            "《亲爱的 热爱的》剧中地图缺少的部分",
            [['海南',100], ['台湾', 100], ['阿克赛钦', 100], ['藏南地区', 100]],
            type_=ChartType.EFFECT_SCATTER,
        )
        # 显示出这个点的标签(formatter={b} 表示显示地区名称,详细可见:
        #  https://pyecharts.org/#/zh-cn/series_options )
        .set_series_opts(label_opts=opts.LabelOpts(is_show=True, formatter="{b}", font_size=14))

        # 设置地图名称,即左上角
        .set_global_opts(title_opts=opts.TitleOpts(title="中国地图"))
    )
    return c    

# 生成对象
c = geo_effectscatter()

# 渲染地图
c.render()

# 生成图片
make_snapshot(snapshot, c.render(), "map_marked.png")

生成的中国地图可是高清大图,高达7M哦!

你要是愿意,甚至可以更改地图的颜色,标点的颜色,每个区域的颜色,甚至可以细化到省级、市级角度,详细请见官方文档:

pyecharts: https://pyecharts.org

如果你想看更多自定义的方法,请阅读:

echarts原始功能: https://echarts.baidu.com

我们的文章到此结束啦!如果你喜欢我们的文章,请持续关注Python实用宝典哦!请记住我们的官方网站:https://pythondict.com , 公众号:python实用宝典。

教你如何使用Python向手机发送通知(IFTTT)

效果图

你曾想尝试在服务器端或电脑上向手机发送通知吗?

你曾烦恼过企业邮箱的防骚扰机制吗?

现在,我们可以用一种简单轻松的方法来代替企业邮箱了!

进行以下的实验,你需要做好以下准备

  • 1)注册并在手机上下载IFTTT
  • 2)Python3

1. 注册配置 IFTTT

首先注册一个IFTTT账号 (https://ifttt.com). 

登录进入页面后点击右上角create,准备新建一个applet. 

进去后点击 + this, 如图。

搜索 webhooks.

进去后选择Receive a web request, 这个trigger能够使得这个webhooks收到一个http请求后触发一个事件。

编写该trigger的名称

然后点击 that.

搜索notification.

选择send a notification from the IFTTT app. 这个action能够使得IFTTT发出一个通知。

里面可以设置消息的格式,其中:{{EventName}}是我们前面设定的事件名称,而Add ingredient里面的value1、value2、value3则是服务器端发送http请求时带的参数。

可以设置成如下的格式:

Finish!

好了,准备完毕,我们开始编写Python脚本了!

2.Python 通知脚本编写

进入 https://ifttt.com/maker_webhooks 页面,你可以看见你刚新建的webhooks. 

点击右上角的Documentation.

Documents

进去之后你就可以看见你关于这个应用的Key. 可以看见其调用方式就是通过发送POST或GET请求到下面这个网址:

https://maker.IFTTT.com/trigger/你的event_name/with/key/你的Key

其中,你还可以带三个参数,以json格式放在body中,如 {“value1”: “这是一个测试”},最终通知里的Value1会被这个value1替代。

get/post

制作通知脚本,例如新建一个文件叫 notice.py如下,text放你想发送的文本,可以把notice.py放在你本机上,也可以放在服务器上结合某种功能。记得先在手机上先下载好IFTTT并登陆

打开CMD(Windows)/Terminal(macos)进入该文件目录,运行:

python notice.py

运行完毕后,手机应当就会收到通知了,如果没有收到通知,请检查你的系统设置有没有给IFTTT通知的权限。

import requests
import json

def send_notice(event_name, key, text):
    """
    通过IFTTT发送手机通知

    Args:
        event_name (str): 事件名称
        key (str): IFTTT上的Key
        text (str): 通知文本信息
    """

    url = f"https://maker.ifttt.com/trigger/{event_name}/with/key/{key}"
    payload = {"value1": text}
    headers = {"Content-Type": "application/json"}
    response = requests.request("POST", url, data=json.dumps(payload), headers=headers)
    print(response.text)
 
text = "603609.SH 特大单资金量急剧上增!"
send_notice('事件名称', 'Key', text)

效果如图:

喜欢的话,欢迎关注微信公众号:Python实用宝典

自动通知系列文章:

让Python自动提醒你:阿森纳进球啦!

Python 自动发送邮件详细教程

教你如何使用Python向手机发送通知(IFTTT)

未来会有更多的有用的Python教程继续放出哦,请持续关注我们的网站和公众号!

python 树莓派人脸识别自动开机教程

是不是厌烦了每次回家都要点击按钮打开电脑的操作?

你如果有看过我以前的推送,是不是厌烦了每次回家都要喊“echo,turn on my pc”,让智能音箱打开电脑的操作?

现在,我们有一个全新的操作,坐到椅子上就能让电脑开机!

(避免你跟我一样,拥有一口蹩脚的英语,让echo听不懂的尴尬)

本教程所需要的工具及应用:

  1. 1.一个树莓派3
  2. 2.一个可在树莓派3上运行的摄像头(我用的是罗技C270,树莓派官方摄像头也可以)
  3. 3.Python3
  4. 4.路由器一台,及支持WakeOnLan的主机(大部分都支持)

如果你只是想在windows/macos上尝试一下人脸识别而不需要进行自动开机,则需要:

1.Python3
2.一个可运行的摄像头

自动开机效果演示:

https://pythondict-1252734158.file.myqcloud.com/home/www/pythondict/wp-content/uploads/2019/08/2019082703565322.mp4

1. 安装必要的python组件

windows/macOS:

pip install opencv-python

非常简单,和树莓派的安装复杂度不是一个级别的。

树莓派

树莓派上的安装过程比较复杂,需要耐心折腾,分为以下步骤:

1.1 安装Cmake等编译openCV源码的工具
sudo apt-get install build-essential cmake pkg-config
1.2 安装几种常见格式的图像操作的包
sudo apt-get install libjpeg-dev libtiff5-dev libjasper-dev libpng12-dev
1.3 安装视频操作的包
sudo apt-get install libavcodec-dev libavformat-dev libswscale-dev libv4l-dev  
sudo apt-get install libxvidcore-dev libx264-dev
1.4 openCV用于图像/GUI展示的功能依赖highgui模块,为了编译它我们需要安装libgtk2.0-dev
sudo apt-get install libgtk2.0-dev
1.5 额外依赖
sudo apt-get install libatlas-base-dev gfortran
1.6 当然,还要安装构建Python扩展所需要的头文件
sudo apt-get install python2.7-dev python3-dev
1.7 下载并编译opencv和opencv_contrib的源码

下载并解压:

cd ~
wget -O opencv.zip https://github.com/opencv/opencv/archive/4.1.0.zip
unzip opencv.zip 
wget -O opencv_contrib.zip https://github.com/Itseez/opencv_contrib/archive/4.1.0.zip
unzip opencv_contrib.zip

编译:

cd ~/opencv-4.1.0/
mkdir build & cd build 
cmake -D ENABLE_PRECOMPILED_HEADERS=OFF \ -D CMAKE_BUILD_TYPE=RELEASE \ -D CMAKE_INSTALL_PREFIX=/usr/local \ -D INSTALL_PYTHON_EXAMPLES=ON \ -D OPENCV_EXTRA_MODULES_PATH=~/opencv_contrib-4.1.0/modules \ -D BUILD_EXAMPLES=ON ..
make -j4 
make install 
make ldconfig

PS:请注意你自己放置的目录和版本的区别,make -j4 是四线程进行编译,过程大约需要2-3个小时,如果说j4编译失败,请去掉-j4这个参数,直接make (单线程,这样大约需要6-9个小时).

2. 测试你的摄像头

编写如下的Python文件,test.py:

运行本程序:

python test.py

如果成功,你将会看到你的摄像头灯亮起(如果有灯的话),屏幕出现两个窗口,一个是彩色的,一个是灰色的。

你还可以在读取到frame后对frame进行操作,如

frame = cv2.flip(frame, -1) # 垂直反转摄像头图形

对摄像头进行垂直翻转。

2. 人脸识别

人脸识别模块我们将使用Haar级联分类器,我们自己搜集人脸图片然后进行训练是比较麻烦的,好在openCV已经提供了相关的人脸识别XML文件,使用这些文件我们就能直接进行人脸或笑脸的识别,下载地址:

https://github.com/opencv/opencv/tree/master/data/haarcascades

我们代码需要用到里面的 haarcascade_frontalface_default.xml ,当然,如果你想尝试别的识别也可以进行下载。

编写如下的Python文件,test2.py:

运行本程序:

python test2.py

如果成功,当有人脸出现在摄像头范围内,则会被用蓝色框框画起来。如图所示:

3. 获取你的人脸数据作为训练集

好了,我们刚刚成功识别了人脸,现在我们需要识别出某个人脸是某个人,比如当我出现在镜头中,它要识别出这个人就是“幻象客”。

新建文件夹train_data,用于保存拍摄下来的人脸,一共拍摄五十张人脸图片,get_train_data.py:

4. 训练刚刚得到的数据

拍摄完我们的图像后,我们还需要对这些图片进行训练,train.py:

训练完成后,当前文件夹会出现trainer.yml文件,这就是我们所需要的模型文件。

5.实体对象并通知设备自动开机

现在我们就可以使用刚刚训练出来的模型文件,对人脸进行检测,以识别出该人脸的实体对象。

recognize.py 代码如下:
如果你只是在windows或者macOS上运行,直接把wake_on_lan函数调用去掉即可。

其中,在标记人脸部分,由于我们的训练集数量少,我把识别到的人脸然后开机的阈值调到了40,避免无法自动开机的尴尬之处,当识别到的人脸的信度大于40,这个人脸对应的名字是我的时候,才会进行开机操作。

这么低的信度也不需要担心识别到别人的脸也开机,经过测试,陌生人的脸大约只有10~20的信度。当然,如果你还是担心,可以把训练集增加,然后调高该判断的信度阈值。

wake_on_lan()函数中的参数,是你需要自动开机的电脑的mac地址。wake on lan 简称WOL,它能让你使用路由器通过LAN端口对某个设备进行开机的操作。本推送中的自动开机使用到的功能就是这个。你需要在路由器的管理页面中,看到本机的mac地址,并送入这个函数中。

树莓派上运行这个Python文件,将脸凑到摄像机前,就会将mac地址对应的设备开机(当然,这个设备要连着路由器才行)。

===========================================

思考一下,其实当训练集够多的时候,准度是相当高的,如果你的照片已经保存在某些数据中,比如尝试过人脸识别通过火车站。那么通过监控摄像头获取你每天的日常路线将轻而易举。这是一件比较可怕的事,我们需要认真思考新时代的人脸识别技术的应用范围了

欢迎查看本系列的其他教程:

系列教程一, 本文章用到了该文章中提到的wake on lan.

1. 利用智能音箱语音控制电脑开关机

系列教程二

2. 语音控制 – 改造普通风扇

系列教程三

3. 语音控制 – 改造普通台灯

我们的文章到此就结束啦,如果你希望我们今天的Python 教程,请持续关注我们,如果对你有帮助,麻烦在下面点一个赞/在看哦有任何问题都可以在下方留言区留言,我们都会耐心解答的!


​Python实用宝典 (pythondict.com)
不只是一个宝典
欢迎关注公众号:Python实用宝典

[准确率:98%] Python 改进朴素贝叶斯自动分类食品安全新闻 实战教程

模型成品在极致安食网:https://jizhianshi.com, 木有太多时间维护。

这是本系列第二篇文章,位于源代码的 2. NB_Weights 中:

https://github.com/Ckend/NLP_DeepLearning_CN_Tutorial

前一篇文章中,我们学习了如何使用朴素贝叶斯自动分类食品安全新闻,准确率为97%,这一篇文章将教大家如何改进这个模型。阅读本篇文章之前,建议先阅读前一篇文章:[准确率:97%] 朴素贝叶斯自动分类食品安全新闻,否则有些概念可能无法理解。

在那篇文章中,在训练的时候,朴素贝叶斯模型中所有词语都是相同的权重,而事实上真的如此吗?我们怎么样才可以知道哪些词语更加重要呢?这时候,数理统计就派上用场了。

我们先对所有的食品安全新闻和非食品安全新闻使用结巴(jieba)分词, 然后统计各个词性在这分别在这两个类别中的数量,比如说名词的结果如下表(使用SPSS得到,其他词性就不一一展示了),显然食品安全新闻中名词的数量多于非食品安全新闻,这也是在人意料之中的结果,但是这并不代表着对于食品安全新闻,名词的重要性就大于其他的词性:

那么如何确定各个词性对分类的重要性呢?单纯根据频率和频数确定是比较复杂的,我们可以尝试使用我们的模型,比如说,先得到一个基准的准确值,然后尝试去除掉名词得到一个准确值,观察这两个准确值的差距,如果非常大,说明名词具有比较重要的地位。我们可以试一下:

在所有词性权重都为1的情况下(基准)进行训练,准确率为:

如果说,名词权重为0呢?

不过,没有对比是没有意义的,我们尝试令形容词权重为0,看看怎么样:

可以看到几乎没有影响,也就是说,对于我们的分类器而言,名词的重要性远远大于形容词。这样,我们可以尝试增加名词的权重,看看效果怎么样:

准确率 0.98,效果显著,不过我们很难确定最佳的权重,只能通过(玄学)调参来找到最合适的权重,你也可以尝试用神经网络来确定权重,虽然容易过拟合,但也是一种方法。不过既然用上了神经网络,就有更优秀的模型可以使用了,我们下次再介绍吧。

接下来讲一下这个权重的实现原理(要是不耐烦,可以直接看文章首行的源代码):基于前一篇文章,我们修改jieba_cut_and_save_file这个函数,这个函数在训练的时候用到了,它修改的就是训练时每个新闻被向量化而成的值,如下所示:

将该函数改成这样:

def jieba_cut_and_save_file(inputList, n_weight, a_weight, output_cleaned_file=False):

   """
   1. 读取中文文件并分词句子
   2. 可以将分词后的结果保存到文件
   """

    output_file = os.path.join('./data/', 'cleaned_' + 'trainMatrix.txt')
    lines = []
    tags = []

    for line in inputList:
        result = pseg.cut(clean_str(line))
        a = []
        b = []
        for word, flag in result:
            # 对分词后的新闻
            if word != ' ':
                # 若非空
                a.append(word)
                if flag.find('n')==0:
                    # 若是名词
                    b.append(n_weight)

                elif flag.find('a')==0:
                    # 若形容词
                    b.append(a_weight)
                else:
                    b.append(1)

        lines.append(a)
        tags.append(b)

    if output_cleaned_file:
        with open(output_file, 'w') as f:
            for line in lines:
                f.write(" ".join(line) + '\n')

    vocabulary = createVocabList(lines)

    # 根据词典生成词向量化器,并进行词向量化
    setOfWords2Vec = setOfWords2VecFactory(vocabulary)
    vectorized = []
    for i,news in enumerate(lines):
        vector = setOfWords2Vec(news, tags[i])
        vectorized.append(vector)

    return vectorized, vocabulary

最后,在主函数调用的时候传入你想需要的参数即可,你可以按照代码,修改任意词性权重,不过要注意结巴的词性表(每种词性的字母),你可以搜索“ictclas 词性表”得到。

import bayes
from data_helpers import *
from sklearn.externals import joblib


posFile = "./data/train_food.txt"
negFile = "./data/train_notfood.txt"



print("正在获取训练矩阵及其分类向量")
trainList,classVec = loadTrainDataset(posFile,negFile)

print("正在将训练矩阵分词,并生成词表")
n_weight = 3
# 名词权重
a_weight = 1
# 形容词权重

vectorized, vocabulary = jieba_cut_and_save_file(trainList, n_weight, a_weight, True)
bayes_ = bayes.oldNB(vocabulary)

# 初始化模型
print("正在训练模型")
bayes_.train(vectorized, classVec)

# 训练
print("保存模型")

joblib.dump(bayes_, "./arguments/train_model.m")

最后总结一下,我们通过修改训练集新闻中不同词性的权重,加大名词权重,从而提高朴素贝叶斯模型的准确率到98%,不过,这个仅仅是在食品安全新闻这个例子中是这样,你如果想要应用到自己的程序上,应该先找到你的关键词性。下一篇文章我们将尝试一些新的模型元素。如果你喜欢的话,请继续关注幻象客。

[准确率:97%] Python 朴素贝叶斯自动分类食品安全新闻

模型成品在极致安食网:https://jizhianshi.com, 木有太多时间维护。

教程源代码点击阅读原文或访问:

https://github.com/Ckend/NLP_DeepLearning_CN_Tutorial

利用朴素贝叶斯来分类食品安全新闻(标题)这种短文本其实精确度并不高,在实际的生产中,由于食品安全和非食品安全的数量差异,我们会发现1000条新闻中可能才出现2条食品安全新闻,也就是说,即便你的模型准确率为95%,1000条新闻中依然会有许多新闻 (50条) 会被错分类,这是一个非常糟糕的结果。因此在生产环境中,如果模型准确率不能高达99%,甚至都无法使用。在我们的研究下,使用朴素贝叶斯,我们的准确率能提高到97%,使用改进的朴素贝叶斯准确率能达到98%以上,使用字符级的卷积神经网络甚至能达到99%,后续的教程我会介绍这两种方法。

为了后续的教程,我们还是先利用最简单的朴素贝叶斯来理解“训练”这个概念。为了尽量简化教程的难度,我尽量不使用数学公式进行讲解,更多的以自然语言和Python代码进行分析。在这个教程中,你所需要的东西有:

1. python 3

2. jieba 分词

3. numpy

4. sklearn (用joiblib保存模型)

训练

朴素贝叶斯的训练,其实就是遍历整个训练集,算出每个词语在不同的分类下出现的概率(该词语/该分类总词数)。最后得到两个向量,这两个向量分别代表了每个词语在食品安全新闻和非食品安全新闻中出现的概率。

for i in range(numTrainNews):

    if classVector[i] == 1:
        p1Num += trainMatrix[i]
        # 每条食品安全新闻中,每个词的数量分布

        p1Sum += sum(trainMatrix[i])
        # 求每条新闻出现的食品安全集合中的词语的数量总合

    else:
        p0Num += trainMatrix[i]
        p0Sum += sum(trainMatrix[i])

p1Vect = log(p1Num / p1Sum)
# 在1的情况下每个词语出现的概率

p0Vect = log(p0Num / p0Sum)
# 在0的情况下每个词语出现的概率

#保存结果
self.p0V = p0Vect
self.p1V = p1Vect

如代码所示,numTrainNews是所有新闻的个数,trainMartix 是新闻进行分词,并数字化后的矩阵(先不用在意这里的数据结构,后面会介绍),其中的[i]代表是第几条新闻,classVector与trainMatrix相对应,它是这些新闻的正确分类(1或0,本例中,1代表食品安全新闻,0代表非食品安全新闻)。

当该新闻是食品安全新闻的时候,即classVector[1] == 1,把它所有词语的频数加到p1Num中, p1Num也是一个向量,保存了所有出现词语的频数。p1Sum是所有词语出现的次数总和。同理,当该新闻不是食品安全新闻的时候,也是使用同样的方式处理。

训练结束后,会得到两个总的概率向量,即p1V和p0V,这两个向量分别代表了每个词语在食品安全新闻和非食品安全新闻中出现的概率。

这里需要注意的是,我们最后对p1V和p0V取对数( np.log( ) )是为了在后续的分类中不会出现许多很小的数(试想一个词在训练集中只出现过1次,但是总词数却有几千个)相乘,这些很小的数相乘可能会导致最后结果被四舍五入为0。取对数能让y值从[0,1]被映射到[-∞, 0],从而避免出现相乘结果被四舍五入的情况。

分类

def classify_vector(self, vec2Classify):
    """
    分类函数,对输入词向量分类
    :param vec2Classify: 欲分类的词向量
    """

    vec2Classify = vec2Classify[0]
    p1 = sum(vec2Classify * self.p1V) + log(self.p1)
    # 元素相乘
    p0 = sum(vec2Classify * self.p0V) + log(1.0 - self.p1)

    if p1 - p0 > 0:
        return 1
    else:
        return 0

vec2Classify 是需要分类的新闻进行分词操作后每个词语在总词表中出现的次数,没有出现的则为0,有出现的为1。以”江西公布食品抽检情况 样品总体合格率95.67%”为例,它分词后的结果是:[‘江西’, ‘公布’, ‘食品’, ‘抽检’, ‘情况’, ‘ ‘, ‘样品’, ‘总体’, ‘合格率’, ‘95.67%’],转换成向量则如下图所示:

其中,像“你们”、“水果”、“购物”这样的词语是不在这条新闻里的,但是它在训练集的其他新闻里,因此总表中会出现它,但是在该新闻的向量中,它会被标记为0. 代表没有出现。

这里还需要注意的是p1这个变量,这个变量叫作“先验概率”,在我们的例子中就是:“现实生活中,多少条新闻里出现一条食品安全新闻”,根据统计,这个数值大概是1/125, 但是我偏向于保守一点的值,因此将其固定设置为1/200. 请注意,我们这里是手动设置先验概率,在scikit-learn等机器学习框架中,他们会根据训练集自动得到这个值,最典型的就是 MultinomialNB 和 GaussianNB,在我们的例子中实际上提升不了多少准确率,因此在这里不讨论。

将当前的词向量和我们刚获得的词向量相乘后,对每个词语得到的值求和,再乘以先验概率就是我们在每个分类上的结果,若该新闻在食品安全的结果大于非食品安全,则被分类为食品安全新闻。

将上面的训练和分类转换成数学公式来进行讲解,那就是这样的:

A:我们需要测试的词语  

B:来自食品安全相关新闻/不是来自食品安全相关新闻的概率。

判断一个单词分别归属两类的概率:

判断一篇新闻是否是食品安全相关的概率:

难怪数学家喜欢写公式,确实比文字简洁很多。


在下面开始讲预处理和模型之前,我需要先把我们的trainNB.py放在这里,好让大家在阅读的过程中回来翻阅,理解预处理和模型中得到的变量是用来干嘛的,以便更好地明白这些代码:

import bayes

from data_helpers import *

from sklearn.externals import joblib

posFile = "./data/train_food.txt"
negFile = "./data/train_notfood.txt"

print("正在获取训练矩阵及其分类向量")
trainList,classVec = loadTrainDataset(posFile,negFile)

print("正在将训练矩阵分词,并生成词表")
vectorized, vocabulary = jieba_cut_and_save_file(trainList,True)

bayes = bayes.oldNB(vocabulary)

# 初始化模型
print("正在训练模型")
bayes.train(vectorized,classVec)

# 训练
print("保存模型")
joblib.dump(bayes, "./arguments/train_model.m")

预处理

讲完训练和测试,你基本上已经知道朴素贝叶斯的运作方式了,接下来我们讲一下如何进行预处理,并从文本中构建词向量和总词表。以下函数都位于GitHub库(见文章首行)的data_helpers.py文件中。

1. 读取训练集

首先从文件中读入我们的训练数据,在我上传的GitHub库(见文章首行)中,我们的训练数据被存在1.NaiveBayes/data/中。

def loadTrainDataset(posFile,negFile):

    """

    便利函数,加载训练数据集

    :param pos: 多少条食品安全相关新闻

    :param neg: 多少条非食品安全相关新闻

    """

    trainingList = []  # 训练集
    classVec = []  # 分类向量
    # 录入食品安全相关的训练集
    posList = list(open(posFile, 'r').readlines())
    posVec = [1] * len(posList)
    trainingList += posList
    classVec += posVec

    # 录入非食品安全相关的训练集
    negList = list(open(negFile, 'r').readlines())
    negVec = [0] * len(negList)
    trainingList += negList
    classVec += negVec

    return trainingList, classVec

2.对新闻进行分词

我们需要对这些新闻进行分词,为了节省以后训练使用的时间,我们还要把分词的结果保存,同样地,这些结果会被保存到data中,名字为cleaned_trainMatrix.txt. 如下所示:

def jieba_cut_and_save_file(inputList, output_cleaned_file=False):

    """

    1. 读取中文文件并分词句子

    2. 可以将分词后的结果保存到文件

    3. 如果已经存在经过分词的数据文件则直接加载

    """

    output_file = os.path.join('./data/', 'cleaned_' + 'trainMatrix.txt')

    if os.path.exists(output_file):
        lines = list(open(output_file, 'r').readlines())
        lines = [line.strip('\n').split(' ') for line in lines]

    else:
        lines = [list(jieba.cut(clean_str(line))) for line in inputList]
        # 将句子进行clean_str处理后进行结巴分词
        lines = [[word for word in line if word != ' '] for line in lines]

    if output_cleaned_file:
        with open(output_file, 'w') as f:
            for line in lines:
                f.write(" ".join(line) + '\n')

    vocabulary = createVocabList(lines)
    # 根据词典生成词向量化器,并进行词向量化
    setOfWords2Vec = setOfWords2VecFactory(vocabulary)
    vectorized = [setOfWords2Vec(news) for news in lines]

    return vectorized, vocabulary

def clean_str(string):
    """
    1. 将除汉字外的字符转为一个空格
    2. 除去句子前后的空格字符
    """

    string = re.sub(r'[^\u4e00-\u9fff]', ' ', string)
    string = re.sub(r'\s{2,}', ' ', string)
    return string.strip()

下面介绍其中出现的createVocabList等函数。

3. 生成词典

还记得我们前面讲到的总词表吗,它记录了所有单词,它包含了所有新闻中出现的,但是不重复的词语。

def createVocabList(news_list):
    """
    从分词后的新闻列表中构造词典
    """
    # 创造一个包含所有新闻中出现的不重复词的列表。
    vocabSet = set([])
    for news in news_list:
        vocabSet = vocabSet | set(news)
        # |取并

    return list(vocabSet)

4.将分词后的新闻向量化

def setOfWords2VecFactory(vocabList):
    """
    通过给定词典,构造该词典对应的setOfWords2Vec
    """

    #优化:通过事先构造词语到索引的哈希表,加快转化
    index_map = {}
    for i, word in enumerate(vocabList):
        index_map[word] = i

    def setOfWords2Vec(news):
        """
        以在构造时提供的词典为基准词典向量化一条新闻
        """

        result = [0]*len(vocabList)
        for word in news:
            #通过默认值查询同时起到获得索引与判断有无的作用
            index = index_map.get(word, None)

            if index:
                result[index] = 1

        return result

    return setOfWords2Vec

5.最后是测试需要用到的向量化新闻

原理和前边对训练集的向量化相似,但是由于是测试时对单个新闻的向量化,因此我们把它分开了。

def vectorize_newslist(news_list, vocabulary):
    """
    将新闻列表新闻向量化,变成词向量矩阵
    注:如果没有词典,默认值为从集合中创造
    """

    # 分词与过滤

    cut_news_list = [list(jieba.cut(clean_str(news))) for news in news_list]

    # 根据词典生成词向量化器,并进行词向量化
    setOfWords2Vec = setOfWords2VecFactory(vocabulary)
    vectorized = [setOfWords2Vec(news) for news in cut_news_list]

    return vectorized, vocabulary

贝叶斯模型(bayes.py)

为了方便保存模型,我们将模型视为一个类,类的初始化如下。

def __init__(self, vocabulary):
    self.p1V = None
    self.p0V = None
    self.p1 = None
    self.vocabulary = vocabulary

其中,vocabulary是在运行完jieba_cut_and_save_file后获得的。在模型初始化的时候我们需要传入vocabulary,见前面的trainNB.py.

1.训练函数

在前面讲解训练的时候我曾经用到这里的一部分代码,下面是全部代码,其实要讲的都已经讲过了,一些细分方面的东西都已经写在注释中了。

def train(self, trainMatrix, classVector):
    """
    训练函数
    :param trainMatrix: 训练词向量矩阵
    :param classVector: 分类向量
    """

    numTrainNews = len(trainMatrix)
    # 多少条新闻

    numWords = len(trainMatrix[0])
    # 训练集一共多少个词语

    # pFood = sum(classVector) / float(numTrainNews)
    # 新闻属于食品安全类的概率
    pFood = float(1)/float(200)

    p0Num = ones(numWords)
    p1Num = ones(numWords)
    p0Sum = 2.0
    p1Sum = 2.0
    # 以上初始化概率,避免有零的存在使后面乘积结果为0

    # self.words_weight

    for i in range(numTrainNews):
        if classVector[i] == 1:
            p1Num += trainMatrix[i]
            # 每条食品安全新闻中,每个词的数量分布
            p1Sum += sum(trainMatrix[i])
            # 求每条新闻出现的食品安全集合中的词语的数量总合

        else:
            p0Num += trainMatrix[i]
            p0Sum += sum(trainMatrix[i])

    p1Vect = log(p1Num / p1Sum)
    # 在1的情况下每个词语出现的概率

    p0Vect = log(p0Num / p0Sum)
    # 在0的情况下每个词语出现的概率

    #保存结果

    self.p0V = p0Vect
    self.p1V = p1Vect
    self.p1 = pFood


2. 分类新闻

测试新闻的时候我们需要用到以下两个函数,一个是用来将新闻词向量化,一个是用来将词向量进行最后的分类操作。

def classify_news(self, news):
    """
    分类函数,对输入新闻进行处理,然后分类
    :param vec2Classify: 欲分类的新闻
    """

    vectorized, vocabulary = vectorize_newslist([news],self.vocabulary)

    return self.classify_vector(vectorized)

def classify_vector(self, vec2Classify):
    """
    分类函数,对输入词向量分类
    :param vec2Classify: 欲分类的词向量
    """

    vec2Classify = vec2Classify[0]
    p1 = sum(vec2Classify * self.p1V) + log(self.p1)
    # 元素相乘

    p0 = sum(vec2Classify * self.p0V) + log(1.0 - self.p1)

    if p1 - p0 > 0:
        return 1
    else:
        return 0

这样我们便完成了训练时所需要的所有函数,接下来尝试训练,在该文件夹中运行python trainNB.py. 训练并保存模型:

成功!接下来我们来测试这个模型:

import bayes
from data_helpers import *
from sklearn.externals import joblib

posFile = "./data/eval_food.txt"
negFile = "./data/eval_notfood.txt"

print("正在得到测试矩阵及其分类向量")
trainList,classVec = loadTrainDataset(posFile,negFile)
nb = joblib.load("arguments/train_model.m")

# 读取模型
results = []
for i in trainList:
    result = nb.classify_news(i)
    results.append(result)

# 测试模型
acc = 0.0
correct = 0
for i in range(len(classVec)):
    if results[i] == classVec[i]:
        correct += 1

acc = correct/len(classVec)

print("正确率为:"+str(acc))

正确率为97.7%.

这只是第一篇教程,后续我们还有办法对食品安全新闻更加精确地进行分类,喜欢大家喜欢,有问题可以在评论区进行讨论。

python 树莓派语音控制普通台灯教程

阅读这篇文章前,这两篇文章可能对你会有所帮助:

利用智能音箱语音控制电脑开关机 (必读,否则你可能不知道我在说什么)

语音控制 – 改造普通风扇 (选读)

先看看效果:

完成这项有趣的实验,你所需要的材料有:

1.电烙铁

2.一个8050三极管

3.一个继电器

4.一个路由器

5.一个树莓派

6.一个智能音箱 (我使用的是亚马逊 Echo Dot2)

7.一个普通台灯

我使用的是最基本的开关台灯,所以它只有一个开关按钮,也只有一个功能,那就是开关台灯(废话),现在我们需要让它能够被语音控制。由于我们可以让智能音箱树莓派通信,所以只需要让台灯连接树莓派就能达成我们的目的,因此我们需要有一个能和这个台灯并联,并且能够接线出来让树莓派控制的器件。那就是继电器。

PS:为什么不能让树莓派像控制风扇一样,直接控制台灯呢?

因为台灯接的是220V电压,树莓派最大电压只有5V,树莓派是较小电流,较小电流要控制大电流则必须用继电器。

加上继电器后,其电路图就如同下面所示。

我们使用的继电器是这个小东西:

但由于我的台灯里的空间不够大(如下图所示,我还打了个孔,方便接出线),我只好拆出这个继电器的核心,当然如果你的台灯够大,就不需要考虑这一点了。

接下来讲一下继电器的使用(原理可见上方动图),其实是很简单的,把点灯开关的两条线接到COM端(共接点)和NO端(常开端),此外DC+, DC-分别连树莓派的5V电源和接地即可。然后从树莓派任意GPIO引脚中引线接继电器的控制端,这个控制端能控制NO端的开闭,当输入1的时候就是闭合,电路连通,灯亮。

不过需要注意的是,树莓派的GPIO口是3.3V的,你需要把它转化成5V才能进行控制,怎么转化呢?那就要使用上我们上一节 语音控制 – 改造普通风扇 讲的三极管了。

接完后如下图所示

这样,硬件方面的工作我们就做完了,接下来是软件方面的工作。

其实也就是让智能音箱识别到一个控制开关的命令,跟 利用智能音箱语音控制电脑开关机 中提到的一样。让树莓派使用fauxmo,模拟成许多智能设备,模拟的配置如下:

"MyLight": {
    "path": "/home/pi/Documents/fauxmo/src/fauxmo/plugins/MyLight.py",
    "DEVICES": [
        {
        "name": "Light",
        "port": 49919,
        "on_cmd": "python2 /home/pi/Documents/Automatic/GPIO13.py on",
        "off_cmd": "python /home/pi/Documents/Automatic/GPIO13.py off",
        "state_cmd": ""
        }
    ]
}

这样,智能音箱就会把这个设备的名字识别为 Light. 因此,当你说

” turn on the light “,

它就会执行on_cmd命令,(在这个例子中,我们的path指向的文件MyLight.py即只是启动一个cmd命令而已)即执行 python GPIO13.py on . 相应地,”turn off the light”则执行off_cmd命令。

GPIO13.py 内只是控制树莓派的开关而已,如下所示,它接收两个参数,on或off. 当参数是on的时候,向树莓派的GPIO33口输出高电平,灯亮。Off的时候则输出低电平,灯灭。

# -*- coding: utf-8 -*-

import RPi.GPIO as GPIO
import time
import sys

# BOARD编号方式,基于插座引脚编号
GPIO.setmode(GPIO.BOARD)

# 输出模式
GPIO.setup(33, GPIO.OUT)

def turnOn():
    GPIO.output(33, GPIO.HIGH)

def turnOff():
    GPIO.output(33, GPIO.LOW)

if sys.argv[1] == 'on':
    turnOn()

elif sys.argv[1] == 'off':
    turnOff()

重启fauxmo,让智能音箱重新搜索,就能找到这个Light的设备,然后说一句 “turn on the light”,就能享受这份折腾的成就感了。

最近我还发现,Amazon Alexa 的手机软件,还支持外网控制这些室内设备,如图。

因此,现在每当我要回到宿舍的时候,在宿舍楼下时,我都会先点击Fan,让它帮我开个风扇,然后根据我是要玩游戏还是要干活,再点击PC或Laptop. 如果是晚上,我还可以点击Light打开点灯。这就是新时代的生活,你得Catch Up。

无论是多么普通的设备,你都可以用一个树莓派把它变得智能。

我们的文章到此就结束啦,如果你希望我们今天的Python 教程,请持续关注我们,如果对你有帮助,麻烦在下面点一个赞/在看哦有任何问题都可以在下方留言区留言,我们都会耐心解答的!


​Python实用宝典 (pythondict.com)
不只是一个宝典
欢迎关注公众号:Python实用宝典

Python 树莓派语音控制普通风扇实现教程

完成这项有趣的实验,你所需要的材料有:

其实也可以改造220V风扇,但是比较复杂,需要多一个继电器,我们下一篇讲改造台灯的时候会说到。建议开始之前先阅读上一篇文章:Python利用智能音箱语音控制电脑开关机 你会得到控制流程的整体思路。

首先是改造风扇,利用树莓派控制开关,我们的改造对象:

完成这项有趣的实验,你所需要的材料有:

1.电烙铁

2.一个8050三极管

3.一个路由器

4.一个树莓派

5.一个智能音箱 (我使用的是亚马逊 Echo Dot2)

6.一个普通的5V风扇 

其实也可以改造220V风扇,但是比较复杂,需要多一个继电器,我们下一篇讲改造台灯的时候会说到。

首先是改造风扇,利用树莓派控制开关,我们的改造对象:

像这种风扇,内部电路其实就是一个开关,它使用USB连接的5V电压,其实和树莓派的5V是一样的。所以我们可以直接使用树莓派供电。但是怎么样才能控制它开关呢?这时候我们就需要8050三极管了。

如图所示,从8050的正面(平)的这一面看过去,三极管中间的那一根即b可以作为控制端,我们可以将它连接到树莓派上的GPIO口,三极管的原理我们可以先跳过,大致先这样理解:当b端为低电平时,电路不通;当b端为高电平时,电路连通;因此,通过控制GPIO口的高低电平便能控制它的开关。我们放弃它原先的电路,使用三极管作为一个新的开关电路,如下图所示,树莓派的5V接风扇,出来后接三极管,然后接负极,b端接GPIO的第12号口,如图所示。

这样,我们通过树莓派控制Pin12#的高低电平就能控制风扇的开关。现实版焊接如图所示(没错,红色的不用管,只是线不够长我又延长了,黑色那块就是三极管,棕色那条是控制端,接树莓派的PIN12#):

进去后,只要把正负连线分别焊接到风扇的正负极即可,如图(其实我这个甚至不用焊,直接连到风扇的正负极就可以了,焊接是怕它掉下来):

大家如果心里没底,可以先用个LED灯接个电阻试一下三极管的控制,成功后就可以大胆上了!

下面是我们的控制代码GPIO12.py:

# -*- coding: utf-8 -*-

import RPi.GPIO as GPIO
import time
import sys

# BOARD编号方式,基于插座引脚编号
GPIO.setmode(GPIO.BOARD)

# 输出模式
GPIO.setup(12, GPIO.OUT)

def turnOn():
    GPIO.output(12, GPIO.HIGH)

def turnOff():
    GPIO.output(12, GPIO.LOW)

if sys.argv[1] == 'on':
    turnOn()

elif sys.argv[1] == 'off':
    turnOff()

文件接收一个参数即 on 或 off. 当参数是on的时候,运行turnOn函数,向pin12#发送高电平,风扇开启。off的时候,则执行turnOff函数,向pin12#发送低电平,风扇关闭。

前面我们已经讲过如何用树莓派控制风扇的开关,接下来是语音控制部分。

在上一篇推送中《利用智能音箱语音控制电脑开关机》我已经介绍了模拟WEMO智能家居软件Fauxmo是如何和亚马逊Echo dot2一起工作的,所以我们这里就不再介绍了,大家如果不理解,可以仔细琢磨一下上一篇推送。

根据前面的代码,我们知道控制风扇开关的是一个Python文件,那么我们可以以这样的逻辑思路完成语音控制

“echo, turn on the fan” -> Echo -> 向树莓派发送指令,执行Python文件,向Pin12#输出高电平,风扇启动

“echo, turn off the fan” -> Echo -> 向树莓派发送指令,执行Python文件,向Pin12#输出低电平,风扇关闭

因此在这里,我们可以继续使用之前提到的commandlineplugin.py文件。音箱收到指令后便查找有没有一个叫 fan 的设备,然后发现(树莓派上)有这样一个设备,便让它通过commandlineplugin.py执行一个文件(GPIO12.py on/off)。

在config.json的PLUGINS下添加下面的代码,注意,port可以随意设置,只要不重复就可以了,Fan 是音箱识别的名字,如果你把它改成baby,那就要 turn on (the) baby 才执行,GPIO12.py你可以放在任何你想要的位置,只要执行的时候写清楚即可,另外如前面所述,文件后面记得写上传递给它的参数。

"MyLight": {
    "path": "/home/pi/Documents/fauxmo/src/fauxmo/plugins/commandlineplugin.py",
    "DEVICES": [
        {
            "name": "Fan",
            "port": 49918,
            "on_cmd": "python2 /home/pi/Documents/Automatic/GPIO12.py on",
            "off_cmd": "python /home/pi/Documents/Automatic/GPIO12.py off",
            "state_cmd": ""
        }
    ]
}

大功告成,重启fauxmo, 然后试试echo, turn on the fan. 你会发现风扇成功运转起来了。如果你是使用的其他智能音箱,请注意,Fauxmo只适用于Echo, 你可以找一个适合于你的智能音箱的,并能让你的树莓派模拟智能家居的工具

风扇只是牛刀小试,下一篇我们将改造台灯,那才是真正的大工程。

我们的文章到此就结束啦,如果你希望我们今天的Python 教程,请持续关注我们,如果对你有帮助,麻烦在下面点一个赞/在看哦有任何问题都可以在下方留言区留言,我们都会耐心解答的!


​Python实用宝典 (pythondict.com)
不只是一个宝典
欢迎关注公众号:Python实用宝典

Python 树莓派智能音箱语音控制电脑开关机

完成下面这个有趣的实验,你所需要的东西有:

1.路由器(能够形成局域网,且电脑已用网线连接)

2.一个智能音箱(本教程使用的是亚马逊 Echo Dot 2)

3.主板支持wake on lan (大部分都支持)

4.一个树莓派

原理流程图如下:

接下里将按照这个流程图进行讲解,首先是智能音箱树莓派的连接,我们需要让树莓派发出信号,让智能音箱将其认作是智能家居并连接。由于我使用的是亚马逊的智能音箱,因此我选择了Fauxmo(https://github.com/n8henrie/fauxmo)非常好用的一个工具

1.在树莓派上安装Fauxmo

Fauxmo的安装方法见下方的操作或网址,注意这里的Python需要3.6以上:

1.git clone https://github.com/n8henrie/fauxmo.git

2.cd fauxmo

3.python3 -m venv .venv

4.source ./.venv/bin/activate

5.pip install -e .[dev]

6.cp config-sample.json config.json

7.根据需求编辑(第一次可先忽略) config.json

8.fauxmo [-v]

成功后,请让智能音箱重新搜索设备,如Echo dot是向他说

“find connected devices”,

或同一局域网下,在网页端(alexa.amazon.com/spa/index.html)上点击Discover devices.

如果它成功发现了新的设备,则安装成功。

2.修改Fauxmo配置,让智能音箱能识别到新的“设备“

Fauxmo其实很简单,我们只需要关注两个地方,一个是源文件下的config.json. 这个文件是用来控制智能音箱能够识别到的设备的。其次是plugins文件夹下的文件,这是树莓派收到指令后将执行的文件。由于开关机我们只需要使用Python进行控制,因此我偷个懒使用了Fauxmo的commandlineplugin(下载地址:https://github.com/n8henrie/fauxmo-plugins/blob/master/commandlineplugin.py). 即树莓派得到指令后就开启命令行,输入相应的操作。

我的PC的config.json配置如下:

"PcControl": {
    "path": "/home/pi/Documents/fauxmo/src/fauxmo/plugins/commandlineplugin.py",
    "DEVICES": [
        {
            "name": "PC",
            "port": 49915,
            "on_cmd":"python2 /home/pi/Documents/Automatic/turnOnPC.py",
            "off_cmd":"python /home/pi/Documents/Automatic/ShutdownAndRebootPC.py 192.168.199.236(该电脑局域网IP) 电脑账号 电脑密码 shutdown",
            "state_cmd": ""
        },
        {
            "name": "RebootPC",
            "port": 49920,
            "on_cmd": "python /home/pi/Documents/Automatic/ShutdownAndRebootPC.py 192.168.199.236 ckend ckend reboot",
            "off_cmd": "python /home/pi/Documents/Automatic/ShutdownAndRebootPC.py 192.168.199.236 ckend ckend reboot",
            "state_cmd": ""
        }
    ]
},

意思就是,如果我向echo说,turn on my PC , 它就通过49915端口访问了plugins文件夹下的commandlineplugin.py,然后on指令的操作是让CMD用python2访问/home/pi/Documents/Automatic/文件夹下的turnOnPC.py文件。

3.增加turnOnPC.py文件和ShutdownAndRebootPC.py文件

turnOnPC.py文件的用处就是通过wake on lan 唤醒电脑,需要提供电脑的Mac地址,不知道的话可以在CMD中输入 ARP -a 查询:

def wake_on_lan(macaddress):

    """ Switches on remote computers using WOL. """

    # Check macaddress format and try to compensate.
    if len(macaddress) == 12:
        pass

    elif len(macaddress) == 12 + 5:
        sep = macaddress[2]
        macaddress = macaddress.replace(sep, '')

    else:
        raise ValueError('Incorrect MAC address format')

    # Pad the synchronization stream.
    data = ''.join(['FFFFFFFFFFFF', macaddress * 20])
    send_data = ''

    # Split up the hex values and pack.
    for i in range(0, len(data), 2):
        send_data = ''.join([send_data,struct.pack('B', int(data[i: i + 2], 16))])

    # Broadcast it to the LAN.
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
    sock.sendto(send_data, ('', 7))

如果你的主板还没有开启WOL,请进入BOOT中开启,非常简单,相关操作可自行查阅主板的说明书。

ShutdownAndRebootPC.py文件主要提供关机和重启的操作,其原理是:通过SSH连接Windows系统执行DOS关机/重启的命令。

def shutdown(ip=sys.argv[1], username=sys.argv[2], password=sys.argv[3]):
    # ssh login
    proc = pexpect.spawn("ssh %s@%s " % (str(username), str(ip)))
    index = proc.expect([".*assword.*", ".*yes.*"])

    if index > 0:
        proc.sendline("yes")
        proc.expect(".*assword.*")

    proc.sendline(password)
    proc.expect(".*你的用户名>.*")
    proc.send("shutdown.exe -s -t 00"+'\r\n')
    # 重启是 shutdown.exe -r -t 00
    time.sleep(1)

上述turnOnPC.py文件和ShutdownAndRebootPC.py文件的源代码在https://github.com/Ckend/Pi-SmartHome 中可以下载,请根据自己的需要进行修改。

将上述文件放到前面指定的/home/pi/Documents/Automatic/下,重启fauxmo,并让智能音箱重新搜索,找到这几个新增的设备,分别是:PC和RebootPC.成功后只要说 “Turn off PC” 即可关闭计算机。

由于fauxmo仅仅适用于亚马逊音箱,所以使用其他音箱的同学可能需要寻找一个相应的模拟WeMo或其他智能设备的项目。接下来还会推送风扇和台灯的改造计划,让普通的它们变成可以语音控制的智能家居。希望各位折腾的开心。


​Python实用宝典 (pythondict.com)

不只是一个宝典

欢迎关注公众号:Python实用宝典、

使用python识别图片每一个像素的RGB颜色

休息了一个月,从今天开始继续更新文章!希望大家喜欢。最近在研究怎么破解验证码,其中就要利用到Python识别像素颜色的技术。你也可以用PS慢慢地进行取样记录,但那样效率非常低下,而且麻烦。识别验证码需要我们高效地提取出像素,并将其转换成黑白二色,这样才好进行矢量对比。

工具

pillow

如果你有PyCharm的话,可以在Preferences内的Project Interpreter中安装pillow.

若你没有PyCharm.请上网查阅安装流程,非常简单。

几个要用到的比较重要的函数(你可以先跳过,遇到不认识的函数再回来查看):

建议直接阅读PIL的英文文档:http://effbot.org/imagingbook/image.htm

image.new(mode,size,color)

使用给定的变量mode和size生成新的图像。mode是图片模式,如”RGB”、”P”。Size是给定的宽/高二元组,这是按照像素数来计算的。对于单通道图像,变量color只给定一个值;对于多通道图像,变量color给定一个元组(每个通道对应一个值)。

im.convert(mode)

将你打开的图片转化为某种格式。

im.getpixel(xy)

返回给定位置的像素。

im.putpixel(xy,color)

修改给定位置的像素。

im.size()

返回两个元素,宽和高。im.size ⇒ (width, height)

若是变量名.size[0]则便是是宽,size[1]则表示是高。

使用的图片:

from PIL import Image

im = Image.open("1.jpg")

for y in range(im.size[1]):
    for x in range(im.size[0]):
        pix = im.getpixel((x,y))
        print(pix)

运行结果很长,我们截取一部分:

不得不说Python实在很强大。不过这样看实在很麻烦,因为像素这么多,我们不可能手动地去分析数据。

如果我们要算出RGB各个位低于100的数量,我们可以这样做:

from PIL import Image
 im = Image.open("1.jpg")
 count = 0
 for y in range(im.size[1]):
     for x in range(im.size[0]):
         pix = im.getpixel((x,y))
         if(pix[0] < 100 and pix[1] < 100 and pix[2] < 100):
             count = count + 1
 print(count)

结果是265743.我们甚至可以把它们转换成别的颜色。

最好是不在原图上操作,我们new一个一样大小的图片即可。

im2 = Image.new(“RGB”,im.size,255)

意思是新建一个跟它一样大的RGB图片,背景颜色为红色。

我们把刚刚各个位低于100的像素转化为白色。

from PIL import Image
im = Image.open("1.jpg")
im2 = Image.new("RGB",im.size,255)
count = 0
for y in range(im.size[1]):
    for x in range(im.size[0]):
        pix = im.getpixel((x,y))
        if(pix[0] < 100 and pix[1] < 100 and pix[2] < 100):
            im2.putpixel((x,y),(255,255,255))
im2.show()

结果:

如果我们对原图进行修改颜色:

嘛,还是可以接受的。

源代码已经上传到公众号的github项目。以后公众号的源代码及图片都能在这里找到:https://github.com/Ckend/GongZhongHao

欢迎关注微信公众号:Python实用宝典https://pythondict.com

Python 画图深入理解递归

效果图,理解本程序递归的时候请盯死它。这篇文章可以说是雪花第三弹了。

当然,这次重点不在于画画,而是在于理解好递归。即便你不会python,在我的注释的帮助下你也可以看懂这个代码(是看懂不是理解)。另外有python的同学可以打进去试试,观察完整绘画过程更有利于理解。

import turtle
def tree(branchLen,t):
#定义一个画画的函数,下面是重点,需要大家自行理解。
    if branchLen > 6:
        t.forward(branchLen)
        #前进branchLen长度
        t.right(20)
        #右转20°
        tree(branchLen-15,t)
        #递归
        t.left(40)
        tree(branchLen-15,t)
        t.right(20)
        t.backward(branchLen)
        #后退branchLen长度
t = turtle.Turtle()
#创造画笔
mytree = turtle.Screen()
#显示可视化窗口
t.left(90)
#转到垂直向上
t.up()
#提起画笔,为了后面调整位置
t.backward(100)
#调整位置
t.down()
#画笔落下
t.color("blue")
#调整画笔颜色
tree(90,t)
#开始画画
mytree.exitonclick()
#防止一画完就自动退出

可能最难理解的部分:

def tree(branchLen,t):
    #定义一个画画的函数,这是个重点,需要大家自行理解。
    if branchLen > 6:
        t.forward(branchLen)
        t.right(20)
        tree(branchLen-15,t)
        t.left(40)
        tree(branchLen-15,t)
        t.right(20)
        t.backward(branchLen)

实际上通过语言形象地把递归讲好是有点难度的,这里我只能尽量解释。首先如果我们设定长度为90.那么第一个递归语句(需要说明的是,第一个递归语句是最后结束的):

        tree(branchLen-15,t)
        t.left(40)

它一共要递归7次,只不过第七次当branchLen = 15 的时候刚好为0不满足条件。我们从这里开始。

此时的位置:

到这里为止,向左转40°

t.left(40)

就是想要画出那个分叉口,但是由于此时的branchLen = 15,当它执行第二个递归语句的时候

tree(branchLen-15,t)

发现无法满足条件,因此又右转20°并返回到原来开始画分叉的那个结点(就是第五个转方向的地点)。

这个小小小小小递归结束,回到branchLen = 30,这时候就能够执行

        t.left(40)
        tree(branchLen-15,t)
        t.right(20)
        t.backward(branchLen)

没错,这里又进行了一次递归。因此它顺利画出分叉:

然后返回到长度为30的那一个结点,如图

此时回到上一层递归,branchLen = 45.又开始一个新的递归,然后这个新的递归里又产生了一个新递归,这个新递归又调用一次递归,最终这个递归无法满足条件回到上一个递归画出了叉口,再返回上一次递归branchLen = 60.然后又开始一个新的递归……

请记住,回归结点的时候从这几行代码开始:        

        t.left(40)
        tree(branchLen-15,t)
        t.right(20)
        t.backward(branchLen)

如果你想很好地理解递归,你可以从这个程序开始。因为它很形象。

欢迎关注微信公众号:Python实用宝典

有趣好用的Python教程

退出移动版