本文正在参与 人工智能创作者扶持计划

一、作用预览

小程序AI初体验 | 做一款实时智能识别菜谱小程序

小程序AI初体验 | 做一款实时智能识别菜谱小程序

1.1 主页引荐图

小程序AI初体验 | 做一款实时智能识别菜谱小程序

1.2 菜谱智能辨认页面

小程序AI初体验 | 做一款实时智能识别菜谱小程序

1.3 菜谱类别列表

小程序AI初体验 | 做一款实时智能识别菜谱小程序

1.4 进程概况

小程序AI初体验 | 做一款实时智能识别菜谱小程序

二、需求背景

每次去饭店吃好吃的,你有没有遇到过一两个让你觉得很想学会做的菜品,这个时分又不好直接去问厨师,又奈何自己的手艺不行! 所以,就算咱们不知道,但咱们总会有方法知道,于是我挑选让AI这位大厨告诉我! 直接经过摄影辨认你想要知道的菜品,就能知道其制造的全流程!直接省去了自己去“下厨房”这一类渠道查找并学习的进程!直接用ai帮你解决全流程!

三、技能架构

在开端介绍本项意图具体完成流程之前,我么先对整个项意图技能栈、项目架构、以及项意图数据流进行熟悉。

3.1 体系整体架构图

小程序AI初体验 | 做一款实时智能识别菜谱小程序

3.2 体系技能栈

前端 后端
– 言语:vue 2.0 | – 框架 : uni-app | – UI组件: uview – 言语:python | – 框架: Flask

3.3 体系数据流通图

本项意图数据流通首要,从小程序端到百度SDK接口端,具体进程如下所示:

小程序AI初体验 | 做一款实时智能识别菜谱小程序

四、体系完成

4.1 小程序端功用完成

4.1.1 菜谱使用主页

页面采用了手机端常用的经典Flex布局,结合Uview组件进行编排。直接上代码:


<template>
	<view>
		<u-navbar back-text="主页" backIconName=" " :backTextStyle="{color:'#ffffff'}" :customBack="back" :background="background" titleColor="#fff">
			<view class="search-wrap" @click="toSearch">
				<u-search height="56" inputAlign="center" placeholder="请输入关键词查找" :searchIconStyle="searchIconStyle" :showAction="false"></u-search>
			</view>
		</u-navbar>
		<view class="home u-p-b-0">
			<u-swiper :list="swiperList"></u-swiper>
			<view class="u-m-t-20">
				<u-grid :col="4" :border="false">
					<u-grid-item v-for="(item,index) in 4" :key="index" @click="$common.navigateTo('/pages/index/list')">
						<u-image :src="categoryimg" borderRadius="50%" width="108" height="108"></u-image>
						<view class="grid-text">类名</view>
					</u-grid-item>
				</u-grid>
			</view>	
		</view>
		<view class="box u-p-t-30">
			<view class="box-side">
				<view class="u-flex u-row-between">
					<view class="title-blod">抢手引荐</view>
					<view class="title-more">更多<u-icon name="arrow-right" color="#18B566" size="24"></u-icon>
					</view>
				</view>
				<view class="">
					<u-grid :col="3" :border="false">
						<u-grid-item v-for="(item,index) in 3" :key="index">
							<u-image width="220" height="220" :src="hotimg" borderRadius="12"></u-image>
						</u-grid-item>
					</u-grid>
				</view>
			</view>
		</view>
		<view class="box">
			<view class="box-side">
				<view class="u-flex u-row-between">
					<view class="title-blod">新品优选</view>
					<view class="title-more">更多<u-icon name="arrow-right" color="#18B566" size="24"></u-icon>
					</view>
				</view>
				<view class="">
					<u-grid :col="3" :border="false">
						<u-grid-item v-for="(item,index) in 3" :key="index">
							<u-image width="220" height="220" :src="newimg" borderRadius="12"></u-image>
						</u-grid-item>
					</u-grid>
				</view>
			</view>
		</view>
		<!-- list -->
		<water :list="flowList"></water>
		<!-- top s -->
		<u-back-top :scrollTop="scrollTop" mode="circle" bottom="200" right="40" top="600" :icon="icon"
			:icon-style="iconStyle" :tips="tips" :custom-style="customStyle">
		</u-back-top>
	</view>
</template>
<script>
	import water from '@/components/water';
	export default {		
		components:{water},
		data() {
			return {
				background: {
					'background-image': 'linear-gradient(45deg, rgb(28, 187, 180), rgb(141, 198, 63))'
				},
				searchIconStyle: {
					'display': 'none'
				},
				swiperList: [
					{image:'/static/banner1.jpg'},
					{image:'/static/banner2.jpg'},
					{image:'/static/banner3.jpg'},
				],
				flowList: [],
				page: 1,
				is_loading: true,
				scrollTop: 0,
				icon: 'arrow-up',
				iconStyle: {
					color: '#ffffff',
					fontSize: '30rpx'
				},
				tips: '顶部',
				customStyle: {
					backgroundColor: '#6BC362',
					color: '#ffffff'
				},
				list:[
					{image:'/static/pic.jpg'},
					{image:'/static/pic2.jpg'},
					{image:'/static/pic3.jpg'},
					{image:'/static/pic4.jpg'},
				],
				hotimg: '/static/pic2.jpg',
				newimg: '/static/pic4.jpg',
				categoryimg: '/static/pic3.jpg'
			}
		},
		onLoad() {
			this.addRandomData()
		},
		methods: {	
			toSearch(){
				this.$common.navigateTo('/pages/index/search')
			},
			addRandomData() {
				for (let i = 0; i < 10; i++) {
					let index = this.$u.random(0, this.list.length - 1);
					// 先转成字符串再转成目标,防止数组目标引证导致数据紊乱
					let item = JSON.parse(JSON.stringify(this.list[index]));
					item.id = this.$u.guid();
					this.flowList.push(item);
				}
			},
			onReachBottom() {
				if (this.is_loading) {
					this.page++;
					this.addRandomData();
				}
			},
			// top
			onPageScroll(e) {
				this.scrollTop = e.scrollTop;
			},
			back() { // 主页
				uni.navigateBack({
					delta: 2
				})
			},
		}
	}
</script>
<style lang="scss" scoped>
	.grid-text {
		font-size: 24rpx;
		margin-top: 4rpx;
		color: $u-type-info;
	}
	.search-wrap {
		margin: 0 30rpx 0 10rpx;
		flex: 1;
	}
	.box{
		background-color: #f7f7f7;
		padding: 15rpx 10rpx;
		.box-side{
			background-color: #fff;
			padding: 15rpx 10rpx 0 10rpx;
			border-radius: 12rpx;
		}
	}
</style>

4.1.2 菜谱辨认功用页

这一页的首要功用在于给用户一个上传菜品相片或者摄影的接口,一起放回体系的辨认成果:

<template>
	<view class="content">
		<text class="slogan">摄影、上传你想了解的菜品</text>
		<image class="image" :src="imageSrc" mode="widthFix"></image>
		<button type="default" class="getBtn" @tap="upload">挑选图片</button>
		<view class="text-area">
			<text class="title">1</text>
			<text class="title">2</text>
			<text class="title">3</text>
		</view>
		<view class="text-area">
			<text class="title">挑选图片</text>
			<text class="title">AI智能辨认</text>
			<text class="title">查看具体进程</text>
		</view>
		<wyb-popup ref="popup" type="bottom" height="400" width="500" radius="6" :showCloseIcon="true">
		    <view class="popup-content">
		          <hm-cover-card :options="options"></hm-cover-card>
		    </view>
		</wyb-popup>
	</view>
</template>
<script>
	import process from '@/utils/QueryResult.js'
	import HmCoverCard from '@/components/hm-cover-card/index.vue'
	import wybPopup from '@/components/wyb-popup/wyb-popup.vue'
	import {
		pathToBase64
	} from '@/js_sdk/mmmm-image-tools/index.js'
	export default {
		data() {
			return {
				title: 'Hello',
				imageSrc: '/static/hotpot.jpeg',
				hash: null,
				  options: {
				          entryPic:
				            '/static/hm-cover-card/images/img_25361_0_1.png',
				          title: '单色摄影',
				          text: '456张景色相片',
				          shoucang:
				            '/static/hm-cover-card/images/img_25361_0_0.png'
				        }
			}
		},
		components: {
		    wybPopup,
			HmCoverCard
		},
		onLoad() {
		},
		methods: {
			upload() {
				let _this = this;
				uni.chooseImage({
					count: 1, //默许9
					sizeType: ['original', 'compressed'], //能够指定是原图还是压缩图,默许二者都有
					sourceType: ['album'], //从相册挑选
					success: function(res) {
						uni.showLoading({
							title: "AI尽力辨认中"
						})
						var result = process(res.tempFilePaths[0])
						uni.getImageInfo({
							src: res.tempFilePaths[0],
							success: (path) => {
								pathToBase64(path.path).then(base64 => {
										_this.imageSrc = base64
										_this.options.entryPic = base64
									})
									.catch(err => {
										console.error(err);
									})
							}
						})
						result.then((res)=>{
							console.log('--result--',JSON.parse(res[1].data))
							uni.hideLoading();
							// _this.options.title = JSON.parse(res[1].data).name[0]
							// _this.options.text = JSON.parse(res[1].data).price[0]
							// _this.$refs.popup.show() // 显现
							_this.$common.navigateTo('/pages/index/detail')
						})
					}
				});
			},
			change() {
				uni.request({
					url: "https://akhaliq-animeganv2.hf.space/api/queue/status/",
					method: 'POST',
					data: {
						"hash": this.hash
					},
					success: (r) => {
						uni.hideLoading()
						// console.log(r.data.data.data[0]);
						this.imageSrc = r.data.data.data[0]
					}
				})
			}
		}
	}
</script>
<style>
	.content {
		display: flex;
		flex-direction: column;
		align-items: center;
		justify-content: center;
		background: linear-gradient(220.55deg, #8FFF85 0%, #39A0FF 100%);
		height: 100vh;
	}
	.image {
		border: 20rpx solid #FFFFFF;
		border-radius: 20rpx;
		height: auto;
		width: 600rpx;
		margin-top: 50rpx;
		margin-left: auto;
		margin-right: auto;
		margin-bottom: 50rpx;
	}
	.text-area {
		display: flex;
		width: 600rpx;
		justify-content: space-around;
		margin-top: 20rpx;
	}
	.title {
		text-align: center;
		font-size: 30rpx;
		color: #ffffff;
	}
	.slogan {
		color: #FFFFFF;
		margin-top: 50rpx;
		font-size: 50rpx;
	}
	.getBtn {
		width: 600rpx;
		border-radius: 50rpx;
		color: #bd2c23;
	}
</style>

4.1.3 菜谱制造概况页

该页展示了怎么制造该菜品的全流程,每一步都能信手捏来:

<template>
	<view>
		<u-navbar back-text="概况" :borderBottom="false" :background="background" :back-text-style="{'color':'#fff'}"
			backIconColor="#fff"></u-navbar>
		<view>
			<u-image :src="foodContent.mainImg" height="550" borderRadius="0"></u-image>
			<view class="home">
				<view class="u-flex u-row-between">
					<view class="title-blod">{{foodContent.name}}</view>
					<view>
						<u-icon name="heart" color="#6BC362" size="40" label="保藏"></u-icon>
					</view>
				</view>
				<view class="u-m-t-30 u-flex u-row-between">
					<view class="times">烹饪时刻:10分钟</view>
					<view class="times">用餐人数:2人</view>
				</view>
				<!-- <view class="u-m-t-20">
					<u-tag v-for="(item,index) in 10" :key="index" text="小吃" mode="dark" class="tag"
						type="success"></u-tag>
				</view> -->
				<view class="u-m-t-10">
					<view class="title-blod">用料</view>
					<view class="u-flex u-row-between u-m-t-10" v-for="(item,index) in 2" :key="index">
						<view>面粉</view>
						<view>100g<text class="texts"
								:class="index/1==0 ? 'bgFA' : 'bg6B'">{{index/1==0 ? '主料' : '辅料'}}</text></view>
					</view>
				</view>
				<view class="u-m-t-30">
					<view class="title-blod">进程</view>
					<view v-for="(item,index) in foodContent.steps" :key="index" class="u-m-t-20 card">
						<view class="flex-process">
							<view>{{item.step}}:{{item.operation}}</view>
						</view>
						<u-image :src="item.img" height="350" border-radius="0"></u-image>
					</view>
				</view>
				<view class="u-m-t-30">
					<view class="title-blod u-m-b-10">介绍</view>
					<view>真好吃</view>
				</view>
				<u-gap height="30"></u-gap>
			</view>
		</view>
		<!-- top s -->
		<u-back-top :scrollTop="scrollTop" mode="circle" bottom="200" right="40" top="600" :icon="icon"
			:icon-style="iconStyle" :tips="tips" :custom-style="customStyle">
		</u-back-top>
	</view>
</template>
<script>
	export default {
		data() {
			return {
				foodContent:{},
				background: {
					'background-image': 'linear-gradient(45deg, rgb(28, 187, 180), rgb(141, 198, 63))'
				},
				detail: {},
				scrollTop: 0,
				icon: 'arrow-up',
				iconStyle: {
					color: '#ffffff',
					fontSize: '30rpx'
				},
				tips: '顶部',
				customStyle: {
					backgroundColor: '#6BC362',
					color: '#ffffff'
				},
			}
		},
		async onLoad() {
		await uni.request({
			url: 'http://127.0.0.1:8099/getLastCook',
			method: 'GET',
			success: (result) => {
				console.log('==== 获得成果 ===',result);
				this.foodContent.mainImg = result.data.body.img
				this.foodContent.name = result.data.body.name
				this.foodContent.steps = result.data.body.steps
				console.log('======',this.foodContent);
				this.$forceUpdate()
			}
		})
		},
		methods: {
			// top
			onPageScroll(e) {
				this.scrollTop = e.scrollTop;
			},
		}
	}
</script>
<style lang="scss" scoped>
	.tag {
		margin: 0 20rpx 20rpx 0;
		display: inline-block;
	}
	.bgFA {
		background: #FFAA3E;
	}
	.texts {
		margin-left: 20px;
		width: 40rpx;
		height: 40rpx;
		color: #FFFFFF;
		border-radius: 24rpx;
		font-size: 26rpx;
		padding: 0 20rpx;
	}
	.textp {
		color: #FFFFFF;
		border-radius: 55rpx;
		font-size: 26rpx;
		margin-right: 10rpx;
		padding: 0 10rpx;
		background-image: linear-gradient(45deg, rgb(28, 187, 180), rgb(141, 198, 63));
	}
	.times {
		width: 49%;
		padding: 20rpx 0;
		background-image: linear-gradient(45deg, rgb(28, 187, 180), rgb(141, 198, 63));
		text-align: center;
		color: #FFFFFF;
		font-size: 24rpx;
	}
	.num {
		z-index: 10;
		left: 14rpx;
		font-size: 24rpx;
	}
	.flex-process {
		display: flex;
		    font-weight: bold;
		    padding-bottom: 10px;
	}
	.card {
		    margin: 5px 5px;
		    padding: 5px 5px;
		    border: 1px solid #eee;
		    border-radius: 12px;
	}
</style>

4.2 Flask服务端功用完成

4.2.1 后端数据传输接口完成

数据传输接口首要包括图片数据的接纳和保存,以及Flask服务的启动和端口指定


from flask import Flask, jsonify, request
import re,os
from aip import AipImageClassify
import RecognizeCar.CrawleCarHome as carhome
from difflib import SequenceMatcher
import ToCook.CookSpider as cookhome
basedir = os.path.abspath(os.path.dirname(__file__))  # 界说一个根目录 用于保存图片用
@app.route('/getLastCook', methods=['GET', 'POST'])
def getLastCook():
    print(last_result)
    return  last_result
@app.route('/uploadCook', methods=['GET', 'POST'])
def recognizeCookImg():
    # 获取图片文件 name = upload
    img = request.files.get('image')
    # 界说一个图片存放的方位 存放在static下面
    path = basedir + "\\"
    # 图片称号
    imgName = img.filename
    # 图片path和称号组成图片的保存途径
    file_path = path + imgName
    # 保存图片
    img.save(file_path)
    """ 读取图片 """
    def get_file_content(filePath):
        with open(filePath, 'rb') as fp:
            return fp.read()
    image = get_file_content(file_path)
    """ 调用菜品辨认 """
    result = client.dishDetect(image)
    print(result['result'][0]['name'])
    # recognized_name = result['result'][0]['name'].replace('_','')
    # print(single_get_first(result['result'][0]['name'].replace('_','')))
    #
    cook_res = cookhome.doSpider(result['result'][0]['name'])
    print(cook_res)
    # for item in car_list1
    #     # print(item['name'][0])
    #     similarity_ratio = SequenceMatcher(None, recognized_name, item['name'][0]).ratio()
    #     if similarity_ratio > 0.7 :
    #         print(item)
    #         return item
    last_result['body']  = cook_res
    return cook_res
if __name__ == '__main__':
    app.run(host="0.0.0.0", port=int("8099"), debug=True)

4.2.2 调用辨认接口功用完成

该辨认接口首要是调用百度SDK的图像辨认接口:

  • 装置图像辨认 Python SDK

图像辨认 Python SDK目录结构

├── README.md
├── aip                   //SDK目录
│   ├── __init__.py       //导出类
│   ├── base.py           //aip基类
│   ├── http.py           //http恳求
│   └── imageclassify.py //图像辨认
└── setup.py              //setuptools装置

支撑Python版本:2.7.+ ,3.+

装置使用Python SDK有如下方法

  • 假如已装置pip,履行pip install baidu-aip即可。
  • 假如已装置setuptools,履行python setup.py install即可。
  • 新建AipImageClassify

AipImageClassify是图像辨认的Python SDK客户端,为使用图像辨认的开发人员提供了一系列的交互方法。

参阅如下代码新建一个AipImageClassify:

    from aip import AipImageClassify
    """ 你的 APPID AK SK """
    APP_ID = '你的 App ID'
    API_KEY = '你的 Api Key'
    SECRET_KEY = '你的 Secret Key'
    client = AipImageClassify(APP_ID, API_KEY, SECRET_KEY)

在上面代码中,常量APP_ID在百度智能云控制台中创建,常量API_KEYSECRET_KEY是在创建结束使用后,体系分配给用户的,均为字符串,用于标识用户,为访问做签名验证,可在AI服务控制台中的使用列表中查看,如下:

小程序AI初体验 | 做一款实时智能识别菜谱小程序

注意:如您以前是百度智能云的老用户,其间API_KEY对应百度智能云的“Access Key ID”,SECRET_KEY对应百度智能云的“Access Key Secret”。

  • 装备AipImageClassify(可选)

假如用户需求装备AipFace的网络恳求参数(一般不需求装备),能够在构造AipFace之后调用接口设置参数,现在只支撑以下参数:

接口 阐明
setConnectionTimeoutInMillis 树立衔接的超时时刻(单位:毫秒
setSocketTimeoutInMillis 经过翻开的衔接传输数据的超时时刻(单位:毫秒)

4.2.3 Python动态爬虫完成

该进程首要是围绕使用Pthon中的Request库爬虫对下厨房的菜品以及制造进程进行实时爬取。

小程序AI初体验 | 做一款实时智能识别菜谱小程序

import requests,json,re
from lxml import etree,html
BASE_URL  = 'https://www.xiachufang.com'
def doSpider(first_tag):
    url = 'https://www.xiachufang.com/search/?keyword={0}'.format(first_tag)
    resp = requests.get(url)
    # html文档
    resp = requests.get(url, headers={
        'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.26 Safari/537.36 Core/1.63.6776.400 QQBrowser/10.3.2601.400',
    })
    # resp.encoding = 'utf-8'
    rest = resp.text
    body = etree.HTML(rest)
    res = body.xpath('/html/body/div[4]/div/div/div[1]/div[1]/div/div[2]/div[1]/ul/li')
    result = []
    for item in res:
        obj = {'name':str.strip(item.xpath('.//p[@class="name"]/a/text()')[0]),
               'stepUrl': BASE_URL + str.strip(item.xpath('.//p[@class="name"]/a/@href')[0]),
               'img': item.xpath('./div/a/div/img/@data-src')[0]
               }
        # hh = html.tostring(,encoding='utf-8').decode('utf-8')
        result.append(obj)
        break
    result[0]['steps'] = crawleDetail(result[0])
    print(result[0])
    return result[0]
def crawleDetail(target):
    resp = requests.get(target['stepUrl'], headers={
        'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.26 Safari/537.36 Core/1.63.6776.400 QQBrowser/10.3.2601.400',
    })
    # resp.encoding = 'utf-8'
    rest = resp.text
    body = etree.HTML(rest)
    source = body.xpath('/html/body/div[4]/div/div/div[1]/div[1]/div/div[5]/table/tr')
    # source_list = []
    # for item in source:
    #     print(html.tostring(item, encoding='utf-8').decode('utf-8'))
    #     print(item.xpath('.//td/a/text()'))
    #     print(item.xpath('.//td/text()'))
    #1、6
    #年CSDN账号码龄,原创高质量博客80余篇,质量分均在90以上。 2、具有网络工程师中级职称以及电子通信工程(大数据引荐体系方向)硕士学位,发表中文中心论文3余篇。 3、研究生期间与朋友就小程序方向进行创业,用户日活达1000 +。 4、现在上任于国内第二队伍互联网公司,任大数据渠道工程师,负责公司风控事务的开发工作,每日处理数据达千万量,有丰厚的技能堆集。
    steps = body.xpath('/html/body/div[4]/div/div/div[1]/div[1]/div/div[6]/ol/li')
    step_list = []
    for index,item in enumerate(steps):
        obj = {'step':index+1,
               'operation':item.xpath('./p/text()')[0],
               'img':item.xpath('./img/@src')[0]}
        step_list.append(obj)
        print(obj)
    return step_list
    # print(hh)
if __name__ == '__main__':
    doSpider('白菜')

五、拓展阅读

入门和进阶小程序开发,不可错误的精彩内容 :

  • 《小程序开发必备功用的吐血收拾【个人中心界面样式大全】》
  • 《微信小程序 | 着手完成双十一红包雨》
  • 《微信小程序 | 人脸辨认的最终解决方案》
  • 《来接私活吧?小程序接私活必备功用-婚恋交友【附完好代码】》
  • 《吐血收拾的几十款小程序登陆界面【附完好代码】》