菜品识别能识别超过 9 千种菜品,支持客户创建属于自己的菜品图库,可准确识别图片中的菜品名称、位置,并获取百科信息,适用于多种客户识别菜品的业务场景中

应用场景

  • 餐饮健康:根据拍摄照片,识别图片中菜品名称,获取菜品参考卡路里含量和百科信息,可结合识别结果进一步提供饮食推荐、健康管理方案等相关功能,增强用户体验,广泛应用于餐饮娱乐类和健康管理类APP中。

接口描述

该请求用于菜品识别。即对于输入的一张图片(可正常解码,且长宽比适宜),输出图片的菜品名称、卡路里信息、置信度。

请求说明

  • HTTP 方法: POST
  • 请求 URL: https://aip.baidubce.com/rest/2.0/image-classify/v2/dish
  • URL参数: access_token
  • Header 参数: Content-Type = application/x-www-form-urlencoded
  • Body 参数:见下表
参数 是否必选 类型 默认值 说明
image string - 图像数据,base64 编码,要求 base64 编码后大小不超过 4M,最短边至少 15px,最长边最大 4096px, 支持 jpg/png/bmp 格式 。注意:图片需要 base64 编码、去掉编码头后再进行 urlencode
top_num unit32 - 返回结果 top n,默认 5
filter_threshold float - 默认 0.95,可以通过该参数调节识别效果,降低非菜识别率
baike_num integer 0 返回百科信息的结果数,默认不返回

返回说明

返回参数如下表:

字段 是否必选 类型 说明
log_id uint64 唯一的 log id,用于问题定位
result_num unit32 返回结果数目,及result数组中的元素个数
result array() 菜品识别结果数组
+name string 菜名,示例:鱼香肉丝
+calorie float 卡路里,每100g的卡路里含量
+probability float 识别结果中每一行的置信度值,0-1
+baike_info object 对应识别结果的百科词条名称
++baike_url string 对应识别结果百度百科页面链接
++image_url string 对应识别结果百科图片链接
++description string 对应识别结果百科内容描述

返回示例如下:

{
"log_id": 7357081719365269362,
"result_num": 5,
"result": [
{
"calorie": "119",
"has_calorie": true,
"name": "酸汤鱼",
"probability": "0.396031"
"baike_info": {
"baike_url": "http://baike.baidu.com/item/%E9%85%B8%E6%B1%A4%E9%B1%BC/1754055",
"description": "酸汤鱼,是黔桂湘交界地区的一道侗族名菜,与侗族相邻的苗、水、瑶等少数民族也有相似菜肴,但其中以贵州侗族酸汤鱼最为有名,据考证此菜肴最早源于黎平县雷洞镇牙双一带。制作原料主要有鱼肉、酸汤、山仓子等香料。成菜后,略带酸味、幽香沁人、鲜嫩爽口开胃,是贵州“黔系”菜肴的代表作之一。这道菜通常先自制酸汤,之后将活鱼去掉内脏,入酸汤煮制。"
}
},
{
"calorie": "38",
"has_calorie": true,
"name": "原味黑鱼煲",
"probability": "0.265432",

},
{
"calorie": "144",
"has_calorie": true,
"name": "椒鱼片",
"probability": "0.0998993"
},
{
"calorie": "98",
"has_calorie": true,
"name": "酸菜鱼",
"probability": "0.0701917"
},
{
"calorie": "257.65",
"has_calorie": true,
"name": "柠檬鱼",
"probability": "0.0471465"
}]
}

C++ 代码实现调用

这里假设已经将环境配置好了,环境配置的文章可以参考 Windows 下使用 Vcpkg 配置百度 AI 图像识别 C++开发环境(VS2017)

为了方便,首先根据返回参数定义了一个结构体,该结构体包括了返回参数中的参数,如下:

struct DishInfo {
std::string name;
bool has_calorie;
float calorie;
float probability;
std::string baikeurl;
std::string imageurl;
std::string baikedesc;

void print() {
std::cout << std::setw(30) << std::setfill('-') << '\n';
std::cout << "name: " << name << "\n";

if (has_calorie)
std::cout << "calorie: " << calorie << "J" << "\n";
else
std::cout << "has_calorie: false\n";

std::cout << "probability: " << probability << "\n";

if (baikeurl != "null")
std::cout << "baikeurl: " << baikeurl << "\n";
if (imageurl != "null")
std::cout << "imageurl: " << imageurl << "\n";
if (baikedesc != "null")
std::cout << "baikedesc: " << baikedesc << "\n";
}
};

DishInfo 结构体中,定义了一个 print 方法以打印获取的结果。

然后定义了一个类来调用接口并获取结果

class Dish
{
public:
Dish();
~Dish();
Json::Value request(std::string imgBase64, std::map<std::string, std::string>& options);

// get all return results
void getAllResult(std::vector<DishInfo>& results);

// only get first result
void getResult(DishInfo& result);


private:
Json::Value obj_;
std::string url_;
// file to save token key
std::string filename_;
};

类中的私有成员 obj_ 表示返回结果对应的 json 对象。url_ 表示请求的 url,filename_ 表示用于存储 access token 的文件的文件名。

request 函数输入请求图像的 base64 编码以及请求参数,返回一个 json 对象,json 对象中包含请求的结果。

getAllResult 获取请求的结果,总共有 top_num 条结果。

getResult 获取 score 最高的一条结果。


完整代码如下

util.hutil.cpp 代码参见 (简单调用篇 01) 通用物体和场景识别高级版 - C++ 简单调用

Dish.h 代码如下:

#pragma once
#include "util.h"

struct DishInfo {
std::string name;
bool has_calorie;
float calorie;
float probability;
std::string baikeurl;
std::string imageurl;
std::string baikedesc;

void print() {
std::cout << std::setw(30) << std::setfill('-') << '\n';
std::cout << "name: " << name << "\n";

if (has_calorie)
std::cout << "calorie: " << calorie << "J" << "\n";
else
std::cout << "has_calorie: false\n";

std::cout << "probability: " << probability << "\n";

if (baikeurl != "null")
std::cout << "baikeurl: " << baikeurl << "\n";
if (imageurl != "null")
std::cout << "imageurl: " << imageurl << "\n";
if (baikedesc != "null")
std::cout << "baikedesc: " << baikedesc << "\n";
}
};

class Dish
{
public:
Dish();
~Dish();
Json::Value request(std::string imgBase64, std::map<std::string, std::string>& options);

// get all return results
void getAllResult(std::vector<DishInfo>& results);

// only get first result
void getResult(DishInfo& result);


private:
Json::Value obj_;
std::string url_;
// file to save token key
std::string filename_;
};

void dishTest();

Dish.cpp 代码如下:

#include "Dish.h"

Dish::Dish()
{
filename_ = "tokenKey";
url_ = "https://aip.baidubce.com/rest/2.0/image-classify/v2/dish";
}


Dish::~Dish()
{
}

Json::Value Dish::request(std::string imgBase64, std::map<std::string, std::string>& options)
{
std::string response;
Json::Value obj;
std::string token;

// 1. get HTTP post body
std::string body;
mergeHttpPostBody(body, imgBase64, options);

// 2. get HTTP url with access token
std::string url = url_;
getHttpPostUrl(url, filename_, token);

// 3. post request, response store the result
int status_code = httpPostRequest(url, body, response);
if (status_code != CURLcode::CURLE_OK) {
obj["curl_error_code"] = status_code;
obj_ = obj;
return obj; // TODO: maybe should exit
}

// 4. make string to json object
generateJson(response, obj);

// if access token is invalid or expired, we will get a new one
if (obj["error_code"].asInt() == 110 || obj["error_code"].asInt() == 111) {
token = getTokenKey();
writeFile(filename_, token);
return request(imgBase64, options);
}

obj_ = obj;
// check for other error code
checkErrorWithExit(obj);

return obj;
}

void Dish::getAllResult(std::vector<DishInfo>& results)
{
int len = obj_["result"].size();
results.reserve(len);
DishInfo tmp;

for (int i = 0; i < len; ++i) {
tmp.name = UTF8ToGB(obj_["result"][i]["name"].asString().c_str());
tmp.has_calorie = obj_["result"][i]["has_calorie"].asBool();
tmp.calorie = stof(obj_["result"][i]["calorie"].asString());
tmp.probability = stof(obj_["result"][i]["probability"].asString());
tmp.baikeurl = obj_["result"][i]["baike_info"].get("baike_url", "null").asString();
tmp.imageurl = obj_["result"][i]["baike_info"].get("image_url", "null").asString();
tmp.baikedesc = UTF8ToGB(obj_["result"][i]["baike_info"].get("description", "null").asString().c_str());
results.push_back(tmp);
}
}

void Dish::getResult(DishInfo & result)
{
result.name = UTF8ToGB(obj_["result"][0]["name"].asString().c_str());
result.has_calorie = obj_["result"][0]["has_calorie"].asBool();
result.calorie = stof(obj_["result"][0]["calorie"].asString());
result.probability = stof(obj_["result"][0]["probability"].asString());
result.baikeurl = obj_["result"][0]["baike_info"].get("baike_url", "null").asString();
result.imageurl = obj_["result"][0]["baike_info"].get("image_url", "none").asString();
result.baikedesc = UTF8ToGB(obj_["result"][0]["baike_info"].get("description", "none").asString().c_str());
}

void dishTest()
{
std::cout << "size: " << sizeof(DishInfo) << "\n";

// read image and encode to base64
std::string imgFile = "./images/dish_test.jpg";
std::string imgBase64;
imgToBase64(imgFile, imgBase64);

// set options
std::map<std::string, std::string> options;
options["baike_num"] = "5"; // default 0
options["top_num"] = "5"; // default 5
options["filter_threshold"] = "0.95"; //default 0.95



Json::Value obj;
Dish dishObj;
obj = dishObj.request(imgBase64, options);

DishInfo result;
dishObj.getResult(result);
result.print();

std::vector<DishInfo> results;
dishObj.getAllResult(results);

for (auto & vec : results) {
vec.print();
}
}

main.cpp 代码如下:

#include "util.h"
#include "Dish.h"
#include <stdlib.h>

int main() {
dishTest();

system("pause");
return EXIT_SUCCESS;
}

运行结果

测试图像