一、效果預(yù)覽
1.1 首頁(yè)推薦圖
1.2 菜譜智能識(shí)別頁(yè)面
1.3 菜譜類別列表
1.4 步驟詳情
二、需求背景
每次去飯店吃好吃的,你有沒有遇到過一兩個(gè)讓你覺得很想學(xué)會(huì)做的菜品,這個(gè)時(shí)候又不好直接去問廚師,又奈何自己的手藝不行!
所以,就算我們不知道,但我們總會(huì)有辦法知道,于是我選擇讓AI
這位大廚告訴我!
直接通過拍照識(shí)別你想要知道的菜品,就能知道其制作的全流程!直接省去了自己去“下廚房”這一類平臺(tái)搜索并學(xué)習(xí)的過程!直接用ai幫你解決全流程!
三、技術(shù)架構(gòu)
在開始介紹本項(xiàng)目的具體實(shí)現(xiàn)流程之前,我么先對(duì)整個(gè)項(xiàng)目的技術(shù)棧、項(xiàng)目架構(gòu)、以及項(xiàng)目的數(shù)據(jù)流進(jìn)行熟悉。
3.1 系統(tǒng)整體架構(gòu)圖
3.2 系統(tǒng)技術(shù)棧
前端 | 后端 |
---|---|
- 語言:vue 2.0 | - 框架 : uni-app | - UI組件: uview | - 語言:python | - 框架: Flask |
3.3 系統(tǒng)數(shù)據(jù)流轉(zhuǎn)圖
本項(xiàng)目的數(shù)據(jù)流轉(zhuǎn)主要,從小程序端到百度SDK接口端,詳細(xì)過程如下所示:
四、系統(tǒng)實(shí)現(xiàn)
4.1 小程序端功能實(shí)現(xiàn)
4.1.1 菜譜應(yīng)用首頁(yè)
頁(yè)面采用了手機(jī)端常用的經(jīng)典Flex布局
,結(jié)合Uview
組件進(jìn)行編排。直接上代碼:
<template>
<view>
<u-navbar back-text="首頁(yè)" backIconName=" " :backTextStyle="{color:'#ffffff'}" :customBack="back" :background="background" titleColor="#fff">
<view class="search-wrap" @click="toSearch">
<u-search height="56" inputAlign="center" placeholder="請(qǐng)輸入關(guān)鍵詞搜索" :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">新品優(yōu)選</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);
// 先轉(zhuǎn)成字符串再轉(zhuǎn)成對(duì)象,避免數(shù)組對(duì)象引用導(dǎo)致數(shù)據(jù)混亂
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() { // 首頁(yè)
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 菜譜識(shí)別功能頁(yè)
這一頁(yè)的主要功能在于給用戶一個(gè)上傳菜品照片或者拍照的接口,同時(shí)放回系統(tǒng)的識(shí)別結(jié)果:
<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智能識(shí)別</text>
<text class="title">查看詳細(xì)步驟</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張風(fēng)景照片',
shoucang:
'/static/hm-cover-card/images/img_25361_0_0.png'
}
}
},
components: {
wybPopup,
HmCoverCard
},
onLoad() {
},
methods: {
upload() {
let _this = this;
uni.chooseImage({
count: 1, //默認(rèn)9
sizeType: ['original', 'compressed'], //可以指定是原圖還是壓縮圖,默認(rèn)二者都有
sourceType: ['album'], //從相冊(cè)選擇
success: function(res) {
uni.showLoading({
title: "AI努力識(shí)別中"
})
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 菜譜制作詳情頁(yè)
該頁(yè)展示了如何制作該菜品的全流程,每一步都能信手捏來:
<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">烹飪時(shí)間:10分鐘</view>
<view class="times">用餐人數(shù):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('==== 獲得結(jié)果 ===',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服務(wù)端功能實(shí)現(xiàn)
4.2.1 后端數(shù)據(jù)傳輸接口實(shí)現(xiàn)
數(shù)據(jù)傳輸接口主要包括圖片數(shù)據(jù)的接收和保存,以及Flask服務(wù)的啟動(dòng)和端口指定
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__)) # 定義一個(gè)根目錄 用于保存圖片用
@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')
# 定義一個(gè)圖片存放的位置 存放在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)
""" 調(diào)用菜品識(shí)別 """
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 調(diào)用識(shí)別接口功能實(shí)現(xiàn)
該識(shí)別接口主要是調(diào)用百度SDK的圖像識(shí)別接口:
- 安裝圖像識(shí)別 Python SDK
圖像識(shí)別 Python SDK目錄結(jié)構(gòu)
├── README.md
├── aip //SDK目錄
│ ├── __init__.py //導(dǎo)出類
│ ├── base.py //aip基類
│ ├── http.py //http請(qǐng)求
│ └── imageclassify.py //圖像識(shí)別
└── setup.py //setuptools安裝
支持Python版本:2.7.+ ,3.+
安裝使用Python SDK有如下方式:
- 如果已安裝pip,執(zhí)行
pip install baidu-aip
即可。 - 如果已安裝setuptools,執(zhí)行
python setup.py install
即可。
- 新建AipImageClassify
AipImageClassify是圖像識(shí)別的Python SDK客戶端,為使用圖像識(shí)別的開發(fā)人員提供了一系列的交互方法。
參考如下代碼新建一個(gè)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
在百度智能云控制臺(tái)中創(chuàng)建,常量API_KEY
與SECRET_KEY
是在創(chuàng)建完畢應(yīng)用后,系統(tǒng)分配給用戶的,均為字符串,用于標(biāo)識(shí)用戶,為訪問做簽名驗(yàn)證,可在AI服務(wù)控制臺(tái)中的應(yīng)用列表中查看,如下:
注意:如您以前是百度智能云的老用戶,其中API_KEY
對(duì)應(yīng)百度智能云的“Access Key ID”,SECRET_KEY
對(duì)應(yīng)百度智能云的“Access Key Secret”。
- 配置AipImageClassify(可選)
如果用戶需要配置AipFace的網(wǎng)絡(luò)請(qǐng)求參數(shù)(一般不需要配置),可以在構(gòu)造AipFace之后調(diào)用接口設(shè)置參數(shù),目前只支持以下參數(shù):
接口 | 說明 |
---|---|
setConnectionTimeoutInMillis | 建立連接的超時(shí)時(shí)間(單位:毫秒 |
setSocketTimeoutInMillis | 通過打開的連接傳輸數(shù)據(jù)的超時(shí)時(shí)間(單位:毫秒) |
4.2.3 Python動(dòng)態(tài)爬蟲實(shí)現(xiàn)
該過程主要是圍繞使用Pthon
中的Request庫(kù)爬蟲
對(duì)下廚房的菜品以及制作步驟進(jìn)行實(shí)時(shí)爬取。
文章來源:http://www.zghlxwxcb.cn/news/detail-402016.html
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賬號(hào)碼齡,原創(chuàng)高質(zhì)量博客80余篇,質(zhì)量分均在90以上。 2、擁有網(wǎng)絡(luò)工程師中級(jí)職稱以及電子通信工程(大數(shù)據(jù)推薦系統(tǒng)方向)碩士學(xué)位,發(fā)表中文核心論文3余篇。 3、研究生期間與朋友就小程序方向進(jìn)行創(chuàng)業(yè),用戶日活達(dá)1000 +。 4、目前就職于國(guó)內(nèi)第二梯隊(duì)互聯(lián)網(wǎng)公司,任大數(shù)據(jù)平臺(tái)工程師,負(fù)責(zé)公司風(fēng)控業(yè)務(wù)的開發(fā)工作,每日處理數(shù)據(jù)達(dá)千萬量,有豐富的技術(shù)積累。
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('白菜')
五、拓展閱讀
??入門和進(jìn)階小程序開發(fā),不可錯(cuò)誤的精彩內(nèi)容?? :文章來源地址http://www.zghlxwxcb.cn/news/detail-402016.html
- 《小程序開發(fā)必備功能的吐血整理【個(gè)人中心界面樣式大全】》
- 《微信小程序 | 動(dòng)手實(shí)現(xiàn)雙十一紅包雨》
- 《微信小程序 | 人臉識(shí)別的最終解決方案》
- 《來接私活吧?小程序接私活必備功能-婚戀交友【附完整代碼】》
- 《吐血整理的幾十款小程序登陸界面【附完整代碼】》
到了這里,關(guān)于微信小程序 |基于Flask框架實(shí)現(xiàn)智能菜譜小程序的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!