上面这三部分第一部分是后端的活儿,其他两部分都是前端的活儿,所以,我在这三块没有太多经验,基本上是面向搜索引擎编程。虽然我的主业是做大数据的,但我确实想做这样一个比较有意思的项目,毕竟一个不会后端的前端不是一个好的大数据工程师。
老规矩,先看效果图,PC版:
小程序:
附上小程序二维码,大家可以体验一下。如果打开看不到效果可能审核没通过,稍微晚点再开即可。
这篇文章会贴比较多的代码,并且公众号阅读起来不是很方便,所以文末我在文末会附上源码的获取方式。(公众号回复关键字 垃圾分类 即可获取整篇文章全部源码)
那么,接下来我们进入到具体的细节是如何做的。其实垃圾分类已经开始很长一段时间了,肯定会有一些服务商把垃圾分类的能力通过API的方式开放出来,供大家调用。我找了3家简单对比下供大家参考:
简单对比了图像分类情况,聚合和天行数据明显更好,再综合定价因素最终我决定用天行数据。下面就来编写代码,将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做的比较挫,交互也比较差,有任何建议也欢迎大家留言。
欢迎公众号「渡码」,输出别地儿看不到的干货。