一、翔云 人工智能开放平台(车牌识别)
二、cJSON 库
三、实现代码
四、回调函数
五、人脸识别和车牌识别获取数据的区别
六、异步网络请求和同步网络请求的区别
七、解耦
一、翔云 人工智能开放平台(车牌识别)
API文档
返回的json数据格式
{
"message": {
"status": "0",
"value": "识别完成"
},
"cardsinfo": [
{
"type": "19",
"items": [
{
"nID": null,
"index": null,
"desc": "车牌号",
"content": "粤A69L59"
},
{
"nID": null,
"index": null,
"desc": "车牌颜色",
"content": "蓝"
},
{
"nID": null,
"index": null,
"desc": "车牌颜色",
"content": "1"
},
{
"nID": null,
"index": null,
"desc": "车牌类型",
"content": "1"
},
{
"nID": null,
"index": null,
"desc": "整牌可信度",
"content": "86"
},
{
"nID": null,
"index": null,
"desc": "亮度评价",
"content": "233"
},
{
"nID": null,
"index": null,
"desc": "车牌运动方向",
"content": "0"
},
{
"nID": null,
"index": null,
"desc": "车牌位置(left_top_right_bottom)",
"content": "103_210_155_86"
},
]
}
]
}
二、cJSON 库
使用的是 cJSON 库,先确保已经安装该库或者将其包含在你的项目中。以下是一种使用 cJSON 库的方法:
-
下载 cJSON 库:
-
你可以从 cJSON 的 GitHub 仓库下载源代码:cjson GitHub Repository。
-
或者使用 git 进行克隆:
git clone https://github.com/DaveGamble/cJSON.git
-
-
将 cJSON 库包含到你的项目中:
- 将 cJSON 目录中的源代码文件(
cJSON.c
和cJSON.h
)复制到你的项目中,或者将整个 cJSON 目录拷贝到你的项目目录。
- 将 cJSON 目录中的源代码文件(
-
修改 Makefile 或编译配置文件:
-
如果你使用 Makefile 进行编译,确保在 Makefile 中添加 cJSON 的源文件。示例 Makefile 可能如下:
CC = gcc CFLAGS = -Wall -I/path/to/cJSON LDFLAGS = -lm SOURCES = your_source_file.c cJSON.c EXECUTABLE = your_executable_name all: $(CC) $(CFLAGS) $(SOURCES) -o $(EXECUTABLE) $(LDFLAGS)
-
如果你使用其他构建工具,确保配置文件中包含 cJSON 的源文件并添加正确的头文件路径。
-
-
编译你的项目:
- 在终端中进入你的项目目录,运行
make
或者你的构建工具指令。
- 在终端中进入你的项目目录,运行
-
链接 cJSON 库:
-
在你的源文件中添加
#include "cJSON.h"
来包含 cJSON 头文件。 -
编译时需要链接 cJSON 库。确保在编译命令中包含 cJSON 的源文件。
-
在上面提供的代码示例中,已经包含了
#include <cjson/cJSON.h>
,并且在编译时需要链接 cJSON 库。
-
以上步骤可能因你的项目和构建系统而异。请根据你的项目结构和构建工具的要求进行相应的修改。
三、实现代码
#include <stdio.h>
#include <curl/curl.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <cjson/cJSON.h>
// 定义bool类型和常量
typedef unsigned int bool; // 数据类型别名用typedef
#define true 1
#define false 0
char PlateInfo[1024] = {'\0'}; // 全局变量,用来接收从JSON处理后返回车牌的数据
// 封装提取键值对的函数
char *getJsonValueByKey(cJSON *root, const char *key)
{
cJSON *item = cJSON_GetObjectItem(root, key);
if (cJSON_IsString(item)) {
return strdup(item->valuestring); // 使用 strdup 复制字符串
}
else {
return NULL;
}
}
// 修改处理JSON数据的函数,使其返回提取的数据
char *processJsonData(const char *jsonString)
{
char *plateNumber = NULL;
char *plateContent = NULL;
// 使用 cJSON 解析 JSON 数据
cJSON *root = cJSON_Parse(jsonString);
if (root == NULL)
{
fprintf(stderr, "Error parsing JSON data\n");
return NULL;
}
// 获取 "cardsinfo" 数组
cJSON *cardsinfoArray = cJSON_GetObjectItem(root, "cardsinfo");
if (cJSON_IsArray(cardsinfoArray) && cJSON_GetArraySize(cardsinfoArray) > 0)
{
cJSON *firstCard = cJSON_GetArrayItem(cardsinfoArray, 0);
// 获取 "items" 数组
cJSON *itemsArray = cJSON_GetObjectItem(firstCard, "items");
if (cJSON_IsArray(itemsArray) && cJSON_GetArraySize(itemsArray) > 0)
{
// 提取车牌号
plateNumber = getJsonValueByKey(itemsArray, "desc");
plateContent = getJsonValueByKey(itemsArray, "content");
}
}
// 释放 cJSON 对象
cJSON_Delete(root);
// 返回提取的数据
PlateInfo = (plateNumber != NULL && plateContent != NULL) ? plateContent : NULL;
return PlateInfo;
}
// 根据文档,接口调用方法为post请求
bool postUrl()
{
CURL *curl;
CURLcode res;
// 根据翔云平台的接口要求 分开定义,然后字符串拼接
char *img = getBase64FromFile("/home/orangepi/smart_home/test/car.jpg"); // 图片base64流
char *key = "JFD5c1iBh9LVqPkkZMxxxx";
char *secret = "76f444813fc945bd9543e4d7e086xxxx";
int typeId = 19;
char *format = "json";
int len = strlen(key) + strlen(secret) + strlen(img) + 128; // 分配空间不够会>导致栈溢出
char* postString = (char*)malloc(len);
memset(postString, '\0', len);//因为postString是一个指针,不能用sizeof来计算其指向的大小
// 字符串拼接
sprintf(postString, "img=%s&key=%s&secret=%s&typeId=%d&format=%s", img, key, secret, typeId, format);
curl = curl_easy_init();
if (curl)
{
// 指定post传输内容,get请求将URL和postString一次性发送
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postString);
// 指定url
curl_easy_setopt(curl, CURLOPT_URL, "https://netocr.com/api/recogliu.do");
// 回调函数读取返回值
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, readData);
// 执行请求
res = curl_easy_perform(curl);
if (res != CURLE_OK) {
fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
// 处理错误
return false;
}
// 处理返回的JSON数据
processJsonData(PlateInfo);
// 清理资源
curl_easy_cleanup(curl);
}
// 释放动态分配的内存
free(img);
free(postString);
return true;
}
int main(void)
{
postUrl();
// 在这里,你可以直接使用 PlateInfo 来获取 OCR 后台返回的车牌信息
printf("车牌号是%d", PlateInfo);
return 0;
}
四、回调函数
回调函数是指在某个特定事件发生时由一个函数调用另一个函数的机制。回调函数通常是作为函数参数传递给其他函数的,以便在特定的事件发生时被调用。这种机制提供了一种灵活的方式,允许在运行时动态指定某个特定事件发生时需要执行的代码。
一般来说,回调函数有以下几个作用:
-
事件处理: 用于处理异步事件,如网络请求完成、用户输入等。当事件发生时,系统调用预先注册的回调函数,执行特定的处理逻辑。
-
定制行为: 允许用户或开发者提供自定义的行为,以便在某个算法、库或框架中插入自定义的逻辑。这提供了一种扩展和定制代码的方式。
-
模块解耦: 回调函数允许将一个模块的实现细节与另一个模块进行解耦。例如,一个库可以提供一个接口,允许用户提供一个回调函数,从而在不修改库源代码的情况下改变其行为。
运用场景包括但不限于:
-
图形用户界面(GUI)编程: 处理按钮点击、菜单选择等用户操作时,通过回调函数来执行相应的处理逻辑。
-
事件驱动编程: 处理异步事件,如文件读写完成、网络请求完成等,通过注册回调函数来响应这些事件。
-
定时器: 在定时器到期时,执行预定的回调函数。
-
数据结构的遍历: 在遍历数据结构时,可以使用回调函数来定义对每个元素的处理逻辑。
-
框架和库设计: 提供可扩展性,允许开发者通过注册回调函数来扩展和定制框架或库的行为。
回调函数在编程中是一种强大而灵活的技术,可以使代码更模块化、可复用和可扩展。
demo
在这个例子中,我们实现了一个简单的计算器,用户可以选择不同的操作,而计算器会执行相应的操作并通过回调函数返回结果。
#include <stdio.h>
// 回调函数的定义
typedef int (*OperationCallback)(int, int);
// 加法回调函数
int add(int a, int b) {
return a + b;
}
// 减法回调函数
int subtract(int a, int b) {
return a - b;
}
// 乘法回调函数
int multiply(int a, int b) {
return a * b;
}
// 除法回调函数
int divide(int a, int b) {
if (b != 0) {
return a / b;
} else {
printf("Error: Division by zero\n");
return 0;
}
}
// 计算器函数,接受两个操作数和一个回调函数
int calculator(int a, int b, OperationCallback callback) {
return callback(a, b);
}
int main() {
int num1, num2;
printf("Enter two numbers: ");
scanf("%d %d", &num1, &num2);
// 提供不同的操作选项
printf("Select operation:\n");
printf("1. Addition\n");
printf("2. Subtraction\n");
printf("3. Multiplication\n");
printf("4. Division\n");
int choice;
scanf("%d", &choice);
// 定义回调函数指针
OperationCallback callback;
// 根据用户选择设置回调函数
switch (choice) {
case 1:
callback = add;
break;
case 2:
callback = subtract;
break;
case 3:
callback = multiply;
break;
case 4:
callback = divide;
break;
default:
printf("Invalid choice\n");
return 1;
}
// 使用回调函数计算并输出结果
int result = calculator(num1, num2, callback);
printf("Result: %d\n", result);
return 0;
}
在这个示例中,定义了四个不同的回调函数(加法、减法、乘法、除法),然后通过 calculator
函数接受两个操作数和一个回调函数,根据用户的选择调用相应的回调函数。这样就可以在运行时指定不同的操作而无需修改主计算器逻辑。这展示了回调函数的灵活性和可扩展性。
五、人脸识别和车牌识别获取数据的区别
-
使用的回调函数是为了在执行 CURL 请求时,通过 CURLOPT_WRITEFUNCTION 选项来指定一个回调函数,将从网络获取的数据写入到 PlateInfo 全局变量中。这样,你可以在请求执行完成后读取 PlateInfo 中的数据,以获取 OCR 后台返回的信息。
-
通过 cJSON 库解析 JSON 数据,然后提取了车牌号的信息,将其存储在 PlateInfo 全局变量中。这样,你不再需要回调函数,因为 processJsonData 直接对返回的 JSON 数据进行解析,并将车牌号信息存储在全局变量中。
两者的区别在于数据获取的时机和方式: -
使用回调函数:
- 时机: 在执行 CURL 请求时,当从网络获取到数据时,回调函数会被调用。
- 方式: 通过
CURLOPT_WRITEFUNCTION
选项,你可以指定一个回调函数,该函数将在每次有新数据到达时被调用。在这个回调函数中,你将数据写入到全局变量PlateInfo
中。这是因为 CURL 在执行请求时是异步的,即它不会等待所有数据都被接收完毕才返回,而是在接收到一部分数据后就触发回调函数。
-
不使用回调函数:
- 时机: 在 CURL 请求执行完成后,你调用
curl_easy_perform
完成请求,并在请求执行完成后直接调用processJsonData
处理返回的 JSON 数据。 - 方式:
processJsonData
函数直接对返回的 JSON 数据进行解析,并将车牌号信息存储在全局变量PlateInfo
中。这里是在同步的程序流程中进行的数据处理,而不需要回调函数。
- 时机: 在 CURL 请求执行完成后,你调用
总的来说,使用回调函数适用于异步的网络请求场景,它允许在数据到达时立即处理,而不必等到整个请求完成。而不使用回调函数,则适用于在请求执行完成后立即处理数据的同步场景。在你的具体应用场景中,选择使用回调函数还是不使用回调函数取决于你对程序执行时机的要求。
回调函数的概念和使用在编程中是一种通用的机制,它可以应用在不同的场景,包括处理用户输入、异步操作、模块解耦、事件驱动编程等。
使用回调函数的主要目的是演示其基本概念和应用,而并非专门与 XML 或 JSON 直接相关。然而,回调函数在处理异步操作、事件处理等场景时,常常与数据格式如 JSON 或 XML 一起使用,以更灵活地处理和解析数据。
具体到 XML 和 JSON,它们是用于表示和传输结构化数据的两种不同格式。XML 使用标签来表示数据结构,而 JSON 使用键值对。在网络通信、数据传输等场景中,你可能会遇到需要通过回调函数处理异步获取的 JSON 或 XML 数据的情况。这时,你可以使用回调函数来定义在数据到达时的处理逻辑。
总体而言,回调函数是一种通用的编程概念,而 XML 和 JSON 则是数据格式,它们可以在不同的上下文中结合使用。
六、异步网络请求和同步网络请求的区别
使用回调函数适用于异步的网络请求场景,它允许在数据到达时立即处理,而不必等到整个请求完成。而不使用回调函数,则适用于在请求执行完成后立即处理数据的同步场景。
异步和同步是两种不同的执行模型,用于描述程序中任务的执行方式。
-
同步(Synchronous):
- 在同步模型中,任务按照固定的顺序顺序执行,一个任务的执行会阻塞后续任务的执行,直到当前任务完成。这意味着程序会按照严格的步骤执行,每一步都需要等待前一步完成。
- 同步操作通常是阻塞的,即程序会等待一个任务完成后再执行下一个任务。这样的模型在代码中通常表现为顺序执行的结构,例如函数调用。
-
异步(Asynchronous):
- 在异步模型中,任务的执行不按照固定的顺序进行,一个任务的开始不会等待前一个任务的完成。程序可以在等待某个任务完成的同时继续执行其他任务。
- 异步操作通常是非阻塞的,即程序可以继续执行其他任务而不必等待当前任务完成。这样的模型在代码中通常表现为回调函数或事件处理,允许在某个事件发生时执行相应的操作。
异步网络请求和同步网络请求的区别:
-
同步网络请求:
- 在发起一个网络请求后,程序会阻塞等待服务器响应。这意味着程序会停止执行,直到请求完成并得到响应。
- 如果网络连接较慢或服务器响应时间较长,程序可能会在等待中花费较长时间,造成用户体验下降。
-
异步网络请求:
- 在发起一个网络请求后,程序不会等待响应。相反,它可以继续执行其他任务。
- 当请求完成并获得响应时,通过回调函数或其他机制执行相应的操作。这种方式允许程序在等待网络响应的同时继续执行其他操作,提高了程序的并发性和响应速度。
应用于编程中的例子:
// 同步网络请求的例子
int syncHttpRequest() {
// 发起网络请求
// 等待服务器响应
// 处理响应数据
return 0;
}
// 异步网络请求的例子
void asyncHttpRequest(void (*callback)(int)) {
// 发起网络请求
// 不等待服务器响应,继续执行其他任务
// 请求完成后,执行回调函数处理响应数据
int responseData = 42; // 模拟响应数据
callback(responseData);
}
// 示例回调函数
void handleResponse(int data) {
// 处理响应数据
printf("Received response: %d\n", data);
}
int main() {
// 同步网络请求
int result = syncHttpRequest();
printf("Result of synchronous request: %d\n", result);
// 异步网络请求
asyncHttpRequest(handleResponse);
printf("Continuing with other tasks...\n");
// 程序继续执行其他任务,不用等待异步请求完成
return 0;
}
在这个例子中,syncHttpRequest
是一个同步的网络请求函数,会阻塞程序直到请求完成。而 asyncHttpRequest
是一个异步的网络请求函数,它不会等待响应,而是通过回调函数的方式在请求完成后处理响应数据。这允许程序在等待网络响应的同时继续执行其他任务。
七、解耦
解耦(Decoupling)是指将一个系统的各个部分(或模块、组件)设计得相对独立,降低它们之间的依赖性,使得一个部分的修改不会直接影响到其他部分。这样的设计能够提高系统的灵活性、可维护性和可扩展性。
在软件开发中,解耦通常有以下几个方面的含义:
-
模块解耦: 将系统划分为相对独立的模块,每个模块负责一个明确的功能,模块之间通过接口进行通信。这样,修改一个模块的实现不会对其他模块产生影响,从而提高了代码的可维护性。
-
组件解耦: 将系统划分为独立的组件,每个组件可以独立开发、测试、部署和升级。组件之间通过定义良好的接口进行通信,使得它们可以被替换或升级而不影响系统的其他部分。
-
时间解耦: 尽量避免在时间上的依赖关系。即,一个操作的完成不依赖于其他操作的执行顺序。这样,系统更具弹性,能够适应不同的执行时序。
-
数据解耦: 降低模块或组件之间的数据依赖,通过定义良好的接口,使得数据的变化不会对其他部分造成影响。这有助于降低系统的耦合度。
解耦的优势包括:
- 可维护性: 模块化和组件化的设计使得系统更容易理解和维护。修改一个模块的实现不会影响其他部分。
- 可扩展性: 可以更容易地添加新的功能或组件,而不必修改现有的代码。
- 灵活性: 系统的不同部分可以独立开发、测试、部署和维护,提高了灵活性。
- 可测试性: 独立的模块或组件更容易进行单元测试,从而提高系统的可测试性。
在编程中,回调函数是一种常见的解耦机制。通过将某一功能的实现通过回调函数传递给其他模块或组件,可以使它们之间解耦。当某一事件发生时,执行相应的回调函数,而不需要直接依赖于特定的模块或组件的实现。
理解解耦的概念可以从以下几个方面入手:
-
独立性: 解耦的核心思想是使系统的各个部分相对独立,一个部分的修改不应该直接影响其他部分。这意味着每个模块、组件或功能都应该尽可能独立,有自己清晰的职责和接口。
-
依赖降低: 解耦的目标之一是降低模块之间的依赖关系。模块之间的耦合越低,修改其中一个模块时对其他模块的影响就越小。这有助于系统更容易扩展、维护和修改。
-
接口定义: 定义清晰的接口是解耦的关键。一个模块通过接口与其他模块通信,而不直接访问其内部实现。这样,当一个模块的内部实现发生变化时,只需要保持接口不变,其他模块就不会受到影响。
-
模块化和组件化设计: 将系统划分为独立的、相对自治的模块或组件。每个模块负责特定的功能,模块之间通过定义的接口进行通信。这样的设计使得系统更容易被理解、维护和扩展。
-
单一职责原则: 每个模块、类或组件应该具有单一职责,即它只负责一个明确的功能。这有助于确保每个部分都是相对独立的,修改一个功能时不会牵扯到其他不相关的功能。
-
事件驱动编程: 在某些情况下,使用事件驱动的方式可以帮助解耦。模块之间通过事件进行通信,而不是直接调用对方的方法。当一个事件发生时,相应的处理逻辑被触发,而不需要知道具体的实现。
-
依赖注入: 通过依赖注入的方式,将一个模块所需要的依赖通过参数传递给它,而不是在模块内部直接创建依赖。这有助于减少模块对特定实现的依赖,提高了灵活性和可替换性。
通过在实际编程中应用这些原则和技术,你可以逐渐培养对解耦概念的理解。在设计和修改系统时,考虑模块之间的独立性、依赖关系、接口设计等因素,以实现更加灵活、可维护和可扩展的代码结构。