最近北京开始实行垃圾分类,导致大家对垃圾的研究热度突然涨高,垃圾们也纷纷表示从来没有获得过这么高的关注度。其实,上海市去年已经开始实行,网上已经有不少成熟的教程了,像什么《垃圾分类从入门到精通》、《深入浅出垃圾分类》、《垃圾分类你应该掌握的10条基本原则》。这种教程如果我们亲自去学显然不符合程序员的个性,作为一个程序员,我们应该把这事儿交给机器来做,这样才能省下更多的时间投入到996中。

扯了这么多废话,下面言归正传,今天这篇文章主要介绍如何利用现有的工具来实现一个垃圾分类的应用。这个想法是我昨天才有的,今天用了不到一天的时间就完成了,主要做了三个核心内容:

  • 对比现有垃圾分类服务,挑选一个合适并编码实现
  • 开发桌面版垃圾分类APP
  • 开发垃圾分类微信小程序

上面这三部分第一部分是后端的活儿,其他两部分都是前端的活儿,所以,我在这三块没有太多经验,基本上是面向搜索引擎编程。虽然我的主业是做大数据的,但我确实想做这样一个比较有意思的项目,毕竟一个不会后端的前端不是一个好的大数据工程师。

老规矩,先看效果图,PC版:

小程序:

附上小程序二维码,大家可以体验一下。如果打开看不到效果可能审核没通过,稍微晚点再开即可。

这篇文章会贴比较多的代码,并且公众号阅读起来不是很方便,所以文末我在文末会附上源码的获取方式。(公众号回复关键字 垃圾分类 即可获取整篇文章全部源码)

那么,接下来我们进入到具体的细节是如何做的。其实垃圾分类已经开始很长一段时间了,肯定会有一些服务商把垃圾分类的能力通过API的方式开放出来,供大家调用。我找了3家简单对比下供大家参考:

  • 聚合数据(www.juhe.cn):提供文本、图像、语音分类。免费调用20次,定价不灵活只能批量购买
  • 天行数据(www.tianapi.com):提供文本、图像、语音分类。文本分类5000次,其他50次,定价按量计费
  • 京东AI开放平台:提供文本、图像、语音分类。免费,每日5000次

简单对比了图像分类情况,聚合和天行数据明显更好,再综合定价因素最终我决定用天行数据
下面就来编写代码,将API接口封装成我们需要的服务,以文本(垃圾名称)分类接口为例,请求的接口如下

http://api.tianapi.com/txapi/lajifenlei/index?key=APIKEY&word=眼镜

APIKEY需要到天行网站注册来获取,返回的结果如下:

{
  "code":200,
  "msg":"success",
  "newslist":[
    {
      "name":"隐形眼镜",
      "type":3,
      "aipre":0,
      "explain":"干垃圾即其它垃圾,指除可回收物、有害垃圾、厨余垃圾(湿垃圾)以外的其它生活废弃物。",
      "contain":"常见包括砖瓦陶瓷、渣土、卫生间废纸、猫砂、污损塑料、毛发、硬壳、一次性制品、灰土、瓷器碎片等难以回收的废弃物",
      "tip":"尽量沥干水分;难以辨识类别的生活垃圾都可以投入干垃圾容器内"
    },
    {
      "name":"眼镜",
      "type":3,
      "aipre":0,
      "explain":"干垃圾即其它垃圾,指除可回收物、有害垃圾、厨余垃圾(湿垃圾)以外的其它生活废弃物。",
      "contain":"常见包括砖瓦陶瓷、渣土、卫生间废纸、猫砂、污损塑料、毛发、硬壳、一次性制品、灰土、瓷器碎片等难以回收的废弃物",
      "tip":"尽量沥干水分;难以辨识类别的生活垃圾都可以投入干垃圾容器内"
    },
  ]
}

接口的字段说明大家可以看官网文档,这里我就不再赘述了。下面来编写请求文本分类接口的代码:

import base64
import requests

class TxApiService:
    def __init__(self):
        self.appkey = 'xxx'  # 需要换成自己的
        self.text_cls_url_root = 'https://api.tianapi.com/txapi/lajifenlei/index?key=%s&word=%s'
        self.img_cls_url_root = 'https://api.tianapi.com/txapi/imglajifenlei/index'

    def get_text_cls_res(self, garbage_name):
        url = self.text_cls_url_root % (self.appkey, garbage_name)
        response = requests.get(url)

        res = []
        if response.status_code == 200:
            res_json = response.json()
            if res_json.get('newslist'):
                new_list_json = res_json['newslist']
                for item in new_list_json:
                    name = item.get('name')
                    cat = self.garbage_id_to_name(item.get('type'))
                    tip = item.get('tip')
                    ai_pre = item.get('aipre')
                    pre_type = 'None'
                    if ai_pre == 0:
                        pre_type = '正常结果'
                    if ai_pre == 1:
                        pre_type = '预判结果'
                    item_dict = {'name': name, 'type': cat, 'tip': tip, 'pre_type': pre_type}
                    res.append(item_dict)
                return res
            else:
                return None
        return None

    def garbage_id_to_name(self, id):
        if id == 0:
            return '可回收物'
        if id == 1:
            return '有害垃圾'
        if id == 2:
            return '厨余垃圾'
        if id == 3:
            return '其他垃圾'
        return None

代码比较简单,用Python的requests库请求垃圾分类接口,并对返回的数据格式化。
下面再来编写请求图像分类的接口

def get_img_cls_res(self, img_base64):
    headers = {
        'Content-Type': 'application/x-www-form-urlencoded'
    }
    body = {
        'key': self.appkey,
        "img": img_base64,
    }
    response = requests.post(self.img_cls_url_root, headers=headers, data=body)

    res = []
    if response.status_code == 200:
        res_json = response.json()
        if res_json.get('newslist'):
            new_list_json = res_json['newslist']
            for item in new_list_json:
                name = item.get('name')
                cat = self.garbage_id_to_name(item.get('lajitype'))
                tip = item.get('lajitip')
                trust = item.get('trust')
                if trust <= 80:
                    continue
                item_dict = {'name': name, 'type': cat, 'tip': tip, 'pre_score': trust}
                res.append(item_dict)
            return res
        else:
            return None
    return None

函数的参数是图像的base64编码,请求方式是POST请求,返回值字段与文本分类略有不同,但思路是一样的。这两部分内容其实比简单,这里我就不再过多解释了。

有了数据服务,下面我们就来开发GUI,这里我用的是tkinter,用它编写的APP可以运行在Linux、Windows和Mac系统,关于tkinter的使用这里我不会做过多介绍,不了解的朋友自行百度,之前我也没结果过基本上看网上的教程照猫画虎。
首选,创建GarbageClassificationApp类,来定义用到的各种组件

import base64
import tkinter

from tkinter import *
import hashlib
import time
from tkinter import filedialog

from TxApiService import TxApiService

class GarbageClassificationApp:
    def __init__(self, tk):
        """
        初始化各个组件
        :param tk:
        """
        self.tk = tk

        # 第一行定义文本分类相关的组件
        self.text_cls_label = Label(self.tk, text="垃圾名:")
        self.garbage_name_text = Entry(self.tk)
        self.text_cls_button = \
            Button(self.tk, text="垃圾名分类", bg="lightblue", width=10, height=1, command=self.garbage_name_cls)

        # 第二行定义图像分类相关的组件
        self.img_cls_label = Label(self.tk, text="垃圾图片:")
        self.select_file_button = Button(self.tk, text='选择图片', command=self.select_pic)
        self.img_cls_button = \
            Button(self.tk, text="图片分类", bg="lightblue", width=10, height=1, command=self.garbage_img_cls)
        self.img_name_text = Text(self.tk, height=2)
        self.img_name_text.insert(1.0, '未选择图片:')
        self.img_name_text['state'] = DISABLED

        # 第三行定义输出结果相关的组件
        self.cls_result_label = Label(self.tk, text="分类结果:")
        self.output_cls_result_list_box = Listbox(self.tk, width=100, height=30)

        # 初始化 api 服务
        self.api_service = TxApiService()

        self.set_init_window()

再来创建set_init_window函数对各个组件进行布局

# 各组件布局
def set_init_window(self):
    self.tk.title("垃圾分类")
    self.tk.geometry('1068x681+350+200')  # 1068x681为窗口大小,+100 +100 定义窗口弹出时的默认展示位置

    # 第一行文本分类各组件的布局
    self.text_cls_label.grid(row=0, column=0, sticky=E)
    self.garbage_name_text.grid(row=0, column=1)
    self.text_cls_button.grid(row=0, column=2, padx=10)

    # 第二行图像分类各组件的布局
    self.img_cls_label.grid(row=1, column=0, sticky=E)
    self.select_file_button.grid(row=1, column=1)
    self.img_cls_button.grid(row=1, column=2, padx=10)
    self.img_name_text.grid(row=1, column=3, padx=10)

    # 第三行输出结果各组件的布局
    self.cls_result_label.grid(row=2, column=0, rowspan=2, sticky=E)
    self.output_cls_result_list_box.grid(row=4, column=1, columnspan=10, pady=10, sticky=E)

这样,界面就完成了。上面定义的一些组件中会有一些事件处理逻辑,比如一个按钮Button被按下时,它就会调用commond属性指定的函数。以文本分类Button为例(text_cls_button),用户按下该按钮后,程序就会执行garbage_name_cls函数,在该函数中我们就可以请求文本分类服务,并将返回的数据显示到界面上。代码如下:

def garbage_name_cls(self):
    garbage_name = self.garbage_name_text.get()
    cat_arr = self.api_service.get_text_cls_res(garbage_name)
    self.output_cls_result_list_box.delete(0, END)

    if cat_arr:
        i = 0
        for item in cat_arr:
            name = '垃圾名称: %s' % item.get('name', 'None')
            self.output_cls_result_list_box.insert(i, name)
            i += 1
            cat = '垃圾类别: %s' % item.get('type', 'None')
            self.output_cls_result_list_box.insert(i, cat)
            i += 1
            pre_type = '预判类型: %s' % item.get('pre_type', 'None')
            self.output_cls_result_list_box.insert(i, pre_type)
            i += 1
            tip = '投放提示: %s' % item.get('tip', 'None')
            self.output_cls_result_list_box.insert(i, tip)
            i += 1

            self.output_cls_result_list_box.insert(i, '')
            i += 1

其他事件处理逻辑类似,代码如下

def select_pic(self):
    """
    单选图片
    :return:
    """
    file_name = filedialog.askopenfilename(
        filetypes=[('图片', ('.png', '.jpg', '.jpeg'))])
    if file_name:
        self.img_name_text['state'] = NORMAL
        self.img_name_text.delete(1.0, END)
        self.img_name_text.insert(1.0, '已选择图片:%s' % file_name)
        self.img_name_text['state'] = DISABLED

def garbage_img_cls(self):
    img_name_text = self.img_name_text.get(1.0, END)
    if img_name_text.startswith('已选择图片:'):
        file_path = img_name_text[6:].strip()
    else:
        return
    with open(file_path, 'rb') as f:
        base64_data = base64.b64encode(f.read())
        img_base64 = base64_data.decode()
    cat_arr = self.api_service.get_img_cls_res(img_base64)
    self.output_cls_result_list_box.delete(0, END)

    if cat_arr:
        i = 0
        for item in cat_arr:
            name = '垃圾名称: %s' % item.get('name', 'None')
            self.output_cls_result_list_box.insert(i, name)
            i += 1
            cat = '垃圾类别: %s' % item.get('type', 'None')
            self.output_cls_result_list_box.insert(i, cat)
            i += 1
            pre_type = '预测得分: %s' % item.get('pre_score', 'None')
            self.output_cls_result_list_box.insert(i, pre_type)
            i += 1
            tip = '投放提示: %s' % item.get('tip', 'None')
            self.output_cls_result_list_box.insert(i, tip)
            i += 1

            self.output_cls_result_list_box.insert(i, '')
            i += 1

至此,PC端桌面APP就开发完成,这里我们没有实现语音分类服务,但思路是一样的,大家可以尝试一下。小程序的代码我就不贴了,我会一起放到源码目录中,在公众号回复关键字 垃圾分类 即可获取整篇文章全部源码。今天开发这个小项目还是花了不少的时间,文章整理出来已经比较晚了,现在是凌晨1点左右,如果又不好理解的后者需要我深入讲解的大家可以给我留言。另外,时间比较紧,所以APP做的比较挫,交互也比较差,有任何建议也欢迎大家留言。

欢迎公众号「渡码」,输出别地儿看不到的干货。

用Python快速实现一个垃圾分类APP|附带微信小程序的更多相关文章

  1. 自家APP打开微信小程序,可行吗?

    小程序的通用解决方案,今天为大家介绍一下FinClip.它的最大特点,就是能够让任何 App 运行小程序. 只需要在你的 App 里面,引入它的 SDK,就能加载运行外部小程序了.除了 SDK,它还提 ...

  2. 如何开发一款堪比APP的微信小程序(腾讯内部团队分享)

    一夜之间,微信小程序刷爆了行业网站和朋友圈,小程序真的能如张小龙所说让用户"即用即走"吗? 其功能能和动辄几十兆安装文件的APP相比吗? 开发小程序,是不是意味着移动应用开发的一次 ...

  3. 手机APP和微信小程序能否取代域名?

    有人说现在App是主流,手机上装几个App就可以了,以后域名的重要性会越来越低,直至App完全取代域名的域名无用论.真的是这样吗? 关于这个话题已经有很多先人前辈探讨过,这次誉名网从另外一个角度给各位 ...

  4. App唤起微信小程序和回调

    在同一开放平台账号下的移动应用及小程序无需关联即可完成跳转,非同一开放平台账号下的小程序需与移动应用(APP)成功关联后才支持跳转. 可在“管理中心-移动应用-应用详情-关联小程序信息”,为通过审核的 ...

  5. 手持式停车收费管理系统全套案例,支持车牌识别,包含了android版app,微信小程序查询,响应式管理后台,云端大数据存储

    先展示几个app效果图片吧,使用起来非常方便,关联了机器的快捷键操作,操作速度提高了不少,摄像头车牌自动识别,车牌识别无网络情况下离线也可以使用   再来一张后台截图,停车场信息完整显示,今日数据实时 ...

  6. uniapp保存图片到本地(APP和微信小程序端)

    uniapp实现app端和微信小程序端图片保存到本地,其它平台未测过,原理类似. 微信小程序端主要是权限需要使用button的开放能力来反复调起,代码如下: 首先是条件编译两个平台的按钮组件: < ...

  7. 【MVVMLight小记】一.快速搭建一个基于MVVMLight的silverlight小程序

    写了篇MVVM小记http://www.cnblogs.com/whosedream/p/mvvmnote1.html,说好要写点MVVMLight的东西,所以接着写,以便和大家共勉. 我假设你已经有 ...

  8. 一个github搞定微信小程序支付系列

    详情请前往github下载示例代码 源码中包含 支付.退款 功能 so easy,项目经理再也不用担心微信支付啦 是的,已经over了

  9. uniapp - 富文本编辑器editor(仅支持App和微信小程序)

    uniapp - editor富文本编辑器用法示例 丢几个图,用心看下去(-.-) 这里使用了https://ext.dcloud.net.cn/plugin?id=412 插件,用于选择字体颜色.其 ...

随机推荐

  1. 关于Python+selenium 定位浏览器弹窗元素

    首先要确定弹窗的类型: (1)div弹窗 (2)新标签页弹窗 (3)alert弹窗 一,div弹窗div弹窗是浏览器中比较好定位的弹窗,定位的方法与普通的元素一样.不过这里会有一个坑,明明可以找到这个 ...

  2. [PHP] 获取IP 和JS获取IP和地址

    通过js获取 服务器 ip 服务器端口 服务器地址 var address=window.location.href; thisDLoc = document.location; var hostpo ...

  3. Spiking-YOLO : 前沿性研究,脉冲神经网络在目标检测的首次尝试 | AAAI 2020

    论文提出Spiking-YOLO,是脉冲神经网络在目标检测领域的首次成功尝试,实现了与卷积神经网络相当的性能,而能源消耗极低.论文内容新颖,比较前沿,推荐给大家阅读   来源:晓飞的算法工程笔记 公众 ...

  4. 【认证与授权】Spring Security的授权流程

    上一篇我们简单的分析了一下认证流程,通过程序的启动加载了各类的配置信息.接下来我们一起来看一下授权流程,争取完成和前面简单的web基于sessin的认证方式一致.由于在授权过程中,我们预先会给用于设置 ...

  5. Flutter Weekly Issue 53

    插件 left-scroll-actions A useful left scroll actions widget like WeChat.一款仿微信效果的 Flutter 左滑菜单插件.现在支持i ...

  6. python学习笔记(三)---字典

    字典 在Python中,字典 字典 是一系列键 键-值对 值对 .每个键 键 都与一个值相关联,你可以使用键来访问与之相关联的值.与键相关联的值可以是数字.字符串.列表乃至字典.事实上,可将 任何Py ...

  7. 作业3-k均值算法

    4. 作业: 1). 扑克牌手动演练k均值聚类过程:>30张牌,3类 2). *自主编写K-means算法 ,以鸢尾花花瓣长度数据做聚类,并用散点图显示.(加分题) 3). 用sklearn.c ...

  8. MySQL事务与并发

      很多程序员都学过MySQL,而且也会写SQL语句.但仅仅会写还远远不够,在面试中以及在工作中,还必须要会事务和并发. 一.事务 事务是满足 ACID 特性的操作,可以通过 Commit 提交事务, ...

  9. 【Linux常见命令】cp命令

    cp - copy files and directories 拷贝文件或目标文件夹,默认不能直接拷贝目录,通过-r参数设置递归复制目录 copy 语法: cp [OPTION]... [-T] SO ...

  10. Vue移动端项目中下拉刷新和上拉加载

    Vue2.0中引入Mint-UI的下拉刷新和上拉加载.简单粗暴 安装Mint-UI npm i mint-ui -S 引入 打开项目的main.js入口文件,引入并使用.注意,为了方便,这里是全部引入 ...