Qt人脸识别与分析系统

news2024/9/21 21:27:54

项目源码地址https://github.com/fufufu11/QT5-FacialDetection

项目概述

本项目是一款基于Qt5框架构建的人脸检测应用程序,支持多摄像头选择和用户友好的图形界面。系统集成了百度的人脸检测API,能够通过HTTPS协议POST方法安全地发送请求,并解析返回的JSON数据来获取人脸特征信息,如年龄、性别、情绪状态及是否佩戴口罩等。为确保高性能和稳定性,项目还采用了多线程技术优化图像处理流程。

主要功能

  • 实时人脸检测:系统能够实时检测并识别来自多个摄像头输入的视频流中的人脸。
  • 人脸属性分析:分析并展示检测到的人脸的详细属性信息。
  • 高效图像处理:通过多线程技术优化了图像处理逻辑,确保在处理大量数据时系统的响应速度和稳定性。

技术细节

  • 开发环境:Windows 11, Qt Creator 12.0.2, Qt 5.15.2 MinGW 64-bit
  • 编程语言:C++
  • 框架与库:Qt5
  • 网络通信:HTTPS
  • 数据处理:JSON解析与Base64编码转换
  • 并发处理:多线程技术

项目亮点

  • 跨摄像头兼容性:支持多种摄像头设备,增强了系统的灵活性和实用性。
  • 安全通信:实现了HTTPS请求的SSL配置,保障了数据传输的安全性。
  • 性能优化:通过多线程技术显著提高了图像处理效率,解决了高负载下的系统卡顿问题

项目实际效果展示

在这里插入图片描述

部分源码分析

调用摄像头功能与绘制人脸框

1.获取所有的摄像头信息

camerasInfo = QCameraInfo::availableCameras();

2.遍历摄像头信息添加到下拉框中

for(const QCameraInfo &cameraInfo : camerasInfo)
{
    // qDebug() << cameraInfo.description();
    ui->comboBox->addItem(cameraInfo.description());
}

遍历camerasInfo列表中的每一个摄像头信息,并将其描述信息(通常是摄像头的名称)添加到UI组件comboBox中。这样用户就可以从下拉列表中选择他们想要使用的摄像头。

3.设置摄像头选择事件处理

connect(ui->comboBox,QOverload<int>::of(&QComboBox::currentIndexChanged),this,&ImageRecognition::pickCamera);

当用户更改comboBox的选择时,会触发pickCamera槽函数。这里使用了QOverload来指定信号的类型。

void ImageRecognition::pickCamera(int idx)
{
    qDebug() << camerasInfo.at(idx).description();
    refreshTimer->stop();
    camera->stop();
    camera = new QCamera(camerasInfo.at(idx));
    imageCapture = new QCameraImageCapture(camera);
    connect(imageCapture,&QCameraImageCapture::imageCaptured,this,&ImageRecognition::showCamera);
    imageCapture->setCaptureDestination(QCameraImageCapture::CaptureToBuffer);
    camera->start();
    refreshTimer->start();
}

在Qt中选择并初始化一个摄像头设备,并设置图像捕获。这个函数pickCamera接收一个索引参数idx,用于从可用的摄像头设备列表中选择一个具体的摄像头,并启动图像捕获。

void ImageRecognition::showCamera(int id,QImage preview)
{
    Q_UNUSED(id);
    img = preview;
    //绘制人脸框
    QPen pen(Qt::white);
    pen.setWidth(5);
    QPainter p(&preview);
    p.setPen(pen);
    p.drawRect(faceLeft,faceTop,faceWidth,faceHeight);
    QFont font;
    font.setPixelSize(50);
    p.setFont(font);
    p.drawText(faceLeft+faceWidth+5,faceTop,QString("年龄:").append(QString::number(age)));
    p.drawText(faceLeft+faceWidth+5,faceTop+50,QString("性别:").append(gender=="male"?"男":"女"));
    p.drawText(faceLeft+faceWidth+5,faceTop+100,QString("情绪:").append(emotion));
    p.drawText(faceLeft+faceWidth+5,faceTop+150,QString("颜值:").append(QString::number(beauty)));
    p.drawText(faceLeft+faceWidth+5,faceTop+200,QString("口罩:").append(mask==1?"佩戴":"未佩戴"));
    p.drawText(faceLeft+faceWidth+5,faceTop+250,QString("眼镜:").append(glasses=="none"?"未佩戴":glasses=="common"?"普通眼镜":"墨镜"));
    p.drawText(faceLeft+faceWidth+5,faceTop+300,QString("真人可能性:").append(QString::number(liveness)));
    ui->label->setPixmap(QPixmap::fromImage(preview));
}

代码段展示了一个名为showCamera的方法,该方法接收一个图像预览(QImage preview)和一个标识符(int id),并在图像上绘制人脸框以及一些与人脸识别相关的文本信息,这些信息是通过解析百度人脸检测平台返回的JSON数据得来的。

4.初始化和摄像头相关的部件

camera = new QCamera();
finder = new QCameraViewfinder();
imageCapture = new QCameraImageCapture(camera);

创建一个QCamera实例、一个QCameraViewfinder实例以及一个QCameraImageCapture实例。

5.设置摄像头预览窗口

camera->setViewfinder(finder);

6.设置摄像头捕捉模式

camera->setCaptureMode(QCamera::CaptureStillImage);

设置摄像头捕捉模式为静止图像模式。

7.设置摄像头捕获目的地

imageCapture->setCaptureDestination(QCameraImageCapture::CaptureToBuffer);

设置图像捕获的目标为内存缓冲区,即捕获的图像是存储在内存中而不是磁盘上。

8.连接图像捕获信号到槽函数

connect(imageCapture,&QCameraImageCapture::imageCaptured,this,&ImageRecognition::showCamera);

当图像被捕获后,imageCaptured信号会被发射,并且连接到了showCamera槽函数。这意呀着每次捕获图像后都会调用showCamera来处理捕获的图像。

9.连接按钮点击事件到槽函数

connect(ui->pushButton,&QPushButton::clicked,this,&ImageRecognition::controlWorker);

当用户点击按钮ui->pushButton时,controlWorker槽函数会被调用。这个函数用了单独的一个线程来执行图像处理有关的任务,也就是单独处理图片转化为Base64编码的操作。

10.启动摄像头

camera->start();

总结来说,这段代码主要实现了从多个摄像头中选择一个,并设置好相应的组件来捕获静止图像的功能。用户可以通过界面选择摄像头,并通过点击按钮来触发图像捕获。捕获后的图像数据将会被传递给showCamera函数处理。

定时器操作(刷新拍照页面和进行人脸识别请求)

1.创建定时器

refreshTimer = new QTimer();
netTimer = new QTimer();

创建两个QTimer对象,分别用于刷新拍照界面和进行人脸识别请求。

2.连接定时器的timeout信号到槽函数

  • 刷新拍照页面
connect(refreshTimer, &QTimer::timeout, this, &ImageRecognition::takePicture);

当refreshTimer的timeout信号被触发时,将调用ImageRecognition类中的takePicture槽函数。

  • 进行人脸识别请求
connect(netTimer, &QTimer::timeout, this, &ImageRecognition::controlWorker);

当netTimer的timeout信号被触发时,将调用ImageRecognition类中的controlWorker槽函数。

  1. 启动定时器
  • 刷新拍照界面定时器
refreshTimer->start(20);

启动refreshTimer定时器,每隔20毫秒触发一次timeout信号。

  • 人脸识别请求定时器
netTimer->start(20);

启动netTimer定时器,每隔20毫秒触发一次timeout信号。

界面布局

    //进行界面布局
    this->resize(1200,700);
    finder->setMinimumSize(600,400);
    QVBoxLayout* vbox = new QVBoxLayout();
    QVBoxLayout* vbox_2 = new QVBoxLayout();
    vbox->addWidget(ui->label);
    vbox->addWidget(ui->pushButton);
    vbox_2->addWidget(ui->comboBox);
    vbox_2->addWidget(finder);
    vbox_2->addWidget(ui->textBrowser);
    QHBoxLayout* hbox = new QHBoxLayout(this);
    hbox->addLayout(vbox);
    hbox->addLayout(vbox_2);
    this->setLayout(hbox);

配置URL以及发送GET请求获取token

1.配置URL

// URL参数设置
QUrl url("https://aip.baidubce.com/oauth/2.0/token");
QUrlQuery query;
query.addQueryItem("grant_type", "client_credentials");
query.addQueryItem("client_id", "oQXBR1aW4BrlvqrvMpCAkyCd");
query.addQueryItem("client_secret", "BxjQcEpqiKDuUR19e1vv3F4CFTBQgPpj");
url.setQuery(query);

这段代码设置了用于向百度AI平台请求访问令牌(access token)的URL及其查询参数。具体来说,这段代码构建了一个带有特定查询参数的URL,用于通过客户端凭证(client credentials)授权方式获取访问令牌。

代码功能概述

  • 创建 URL:创建一个 QUrl 对象,指向百度AI平台的 OAuth 2.0 访问令牌端点。
  • 设置查询参数:创建一个 QUrlQuery 对象,并添加必要的查询参数。设置 grant_type 为 client_credentials,表示使用客户端凭证授权方式。设置 client_id 和 client_secret,这两个参数是百度AI平台为你的应用分配的唯一标识符和密钥。
  • 合并查询参数到 URL:将 QUrlQuery 对象中的查询参数附加到 QUrl 对象中。

2.SSL配置
因为处理的是 HTTPS 请求,所以需要配置一下SSL,设置TLS,需要加入必要的库,可以参考这篇文章https://blog.csdn.net/gm2418/article/details/129865506

    //SSL配置
    sslConfig = QSslConfiguration::defaultConfiguration();
    sslConfig.setPeerVerifyMode(QSslSocket::QueryPeer);
    sslConfig.setProtocol(QSsl::TlsV1_2);
    //组装请求
    QNetworkRequest req;
    req.setUrl(url);
    req.setSslConfiguration(sslConfig);
    //发送get请求
    tokenManager->get(req);
  • 代码解释
    SSL配置:
    获取默认的SSL配置。
    设置对等验证模式为QueryPeer。
    设置TLS协议版本为TlsV1_2。
  • 组装请求:
    创建一个QNetworkRequest对象。
    设置请求的目标URL。
    设置请求的SSL配置。
  • 发送GET请求:
    使用QNetworkAccessManager发送GET请求

访问 token 请求及相应处理

1.创建网络访问管理器

tokenManager = new QNetworkAccessManager(this);
connect(tokenManager, &QNetworkAccessManager::finished, this, &ImageRecognition::tokenReply);

创建一个QNetworkAccessManager对象,并将其与槽函数tokenReply连接起来。这样,当网络请求完成时,QNetworkAccessManager会触发finished信号,从而调用tokenReply函数来处理回复。

2.定义槽函数tokenReply

void ImageRecognition::tokenReply(QNetworkReply *reply)
{
    // 错误处理
    if (reply->error() != QNetworkReply::NoError)
    {
        qDebug() << reply->errorString();
        return;
    }

    // 正常应答
    const QByteArray reply_data = reply->readAll();

    // JSON解析
    QJsonParseError jsonErr;
    QJsonDocument doc = QJsonDocument::fromJson(reply_data, &jsonErr);

    // 解析成功
    if (jsonErr.error == QJsonParseError::NoError)
    {
        QJsonObject obj = doc.object();
        if (obj.contains("access_token"))
        {
            accessToken = obj.take("access_token").toString();
        }
        ui->textBrowser->setText(accessToken);
    }
    // 解析失败
    else
    {
        qDebug() << "JSON ERR:" << jsonErr.errorString();
    }

    netTimer->start(1000);
    reply->deleteLater();
}
  • 错误处理
if (reply->error() != QNetworkReply::NoError)
{
    qDebug() << reply->errorString();
    return;
}

检查网络请求是否出现错误。如果请求失败,打印错误信息并提前返回。

2.读取回复数据

const QByteArray reply_data = reply->readAll();

读取网络请求的回复数据,并将其存储在一个QByteArray对象中。

3.JSON解析

QJsonParseError jsonErr;
QJsonDocument doc = QJsonDocument::fromJson(reply_data, &jsonErr);

尝试将回复数据解析为一个QJsonDocument对象。QJsonParseError对象用于记录解析过程中可能发生的错误。

4.检测解析结果

if (jsonErr.error == QJsonParseError::NoError)
{
    QJsonObject obj = doc.object();
    if (obj.contains("access_token"))
    {
        accessToken = obj.take("access_token").toString();
    }
    ui->textBrowser->setText(accessToken);
}
else
{
    qDebug() << "JSON ERR:" << jsonErr.errorString();
}

如果解析成功,从JSON对象中提取access_token字段,并将其存储在accessToken变量中。
将提取到的access_token显示在textBrowser中。
如果解析失败,打印解析错误信息。

  1. 重新启动定时器
netTimer->start(1000);

重新启动定时器netTimer,使其每隔1000毫秒(1秒)再次触发请求。

6.释放资源

reply->deleteLater();

释放QNetworkReply对象,避免内存泄漏。

这段代码主要用于处理网络请求的回复,并从回复中提取JSON格式的数据。具体来说,它实现了以下功能:

  • 检查网络请求是否成功。
  • 读取并解析网络请求的回复数据。
  • 提取JSON数据中的access_token字段。
  • 显示提取到的access_token。
  • 重新启动定时器以继续发起新的请求。
  • 释放网络请求对象以避免内存泄漏。

多线程处理BASE64编码并POST发送

1.定时器设置

// 利用定时器不断进行人脸识别请求
netTimer = new QTimer();
connect(netTimer, &QTimer::timeout, this, &ImageRecognition::controlWorker); // 因为转换成 base64 编码会造成卡顿,把这部分放到线程里执行
  • 创建定时器:
    • netTimer = new QTimer(); 创建一个新的QTimer对象。
  • 连接信号和槽:
    • connect(netTimer, &QTimer::timeout, this, &ImageRecognition::controlWorker); 将定时器的timeout信号连接到ImageRecognition类的controlWorker槽函数。每当定时器超时时,就会触发controlWorker方法,从而开始新的一轮人脸识别处理。

2.controlWorker方法

void ImageRecognition::controlWorker()
{
    childThread = new QThread(this);
    Worker* worker = new Worker;
    worker->moveToThread(childThread);
    connect(this, &ImageRecognition::operate, worker, &Worker::doWork);
    connect(worker, &Worker::resultReady, this, &ImageRecognition::beginFaceDetect);
    connect(childThread, &QThread::finished, worker, &QObject::deleteLater);
    childThread->start();
    emit operate(img, childThread);
}
  • 创建新的线程:
    • childThread = new QThread(this); 创建一个新的QThread对象。
  • 创建Worker对象:
    • Worker* worker = new Worker; 创建一个新的Worker对象。
  • 将Worker对象移动到新线程:
    • worker->moveToThread(childThread); 将Worker对象移动到新创建的线程childThread中。
  • 连接信号和槽:
    • connect(this, &ImageRecognition::operate, worker, &Worker::doWork); 连接ImageRecognition类的operate信号到Worker类的doWork槽函数。
    • connect(worker, &Worker::resultReady, this, &ImageRecognition::beginFaceDetect); 连接Worker类的resultReady信号到ImageRecognition类的beginFaceDetect槽函数。
    • connect(childThread, &QThread::finished, worker, &QObject::deleteLater); 连接QThread的finished信号到Worker对象的deleteLater槽函数,确保当线程结束后,Worker对象会被自动删除。
  • 启动线程并开始工作:
    • childThread->start(); 启动新线程。
    • emit operate(img, childThread); 发射operate信号,传递当前图像img和新线程childThread作为参数,触发Worker对象中的doWork方法开始执行。

3.Worker::doWork方法

void Worker::doWork(QImage img, QThread* childThread)
{
    // 转换成 base64 编码
    QByteArray ba;
    QBuffer buff(&ba);
    img.save(&buff, "png");
    QString b64str = ba.toBase64();

    // 请求体 body 参数设置
    QJsonObject postJson;
    QJsonDocument doc;
    postJson.insert("image", b64str);
    postJson.insert("image_type", "BASE64");
    postJson.insert("face_field", "age,expression,face_shape,gender,glasses,quality,eye_status,emotion,face_type,mask,beauty");
    postJson.insert("liveness_control", "NORMAL");
    doc.setObject(postJson);
    QByteArray postData = doc.toJson(QJsonDocument::Compact);
    emit resultReady(postData, childThread);
}
  • 转换图像为Base64编码:
    • QByteArray ba; 创建一个字节数组。
    • QBuffer buff(&ba); 创建一个缓冲区QBuffer,并将其关联到字节数组ba。
    • img.save(&buff, “png”); 将QImage对象保存为PNG格式到QBuffer中。
      QString b64str = ba.toBase64(); 将字节数组转换为Base64编码的字符串。
  • 构建请求体:
    • 创建一个QJsonObject对象,并插入必要的键值对,包括图像的Base64编码、图像类型以及其他人脸识别所需的信息。
    • 创建一个QJsonDocument对象,并设置其内容为QJsonObject。
    • 将QJsonDocument转换为QByteArray格式。
    • 发射resultReady信号,传递处理好的postData和当前线程childThread作为参数。

4.beginFaceDetect方法

void ImageRecognition::beginFaceDetect(QByteArray postData, QThread* overThread)
{
    overThread->exit();
    overThread->wait();
    // 组装图像识别请求
    QUrl url("https://aip.baidubce.com/rest/2.0/face/v3/detect");
    QUrlQuery query;
    query.addQueryItem("access_token", accessToken);
    url.setQuery(query);
    // 组装请求
    QNetworkRequest req;
    req.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/json"));
    req.setUrl(url);
    req.setSslConfiguration(sslConfig);
    imgManager->post(req, postData);
}
  • 退出并等待线程:
    • overThread->exit(); 使正在运行的子线程退出。
    • overThread->wait(); 等待子线程完全退出,确保所有在该线程中运行的任务都已完成。
  • 组装请求URL:
    • 创建一个QUrl对象,指定百度AI平台的API端点地址。
    • 创建一个QUrlQuery对象,并向其中添加access_token查询参数。
    • 将查询参数应用到URL对象上,形成最终的请求URL。
  • 创建网络请求:
    • 创建一个QNetworkRequest对象。
    • 设置请求的内容类型为application/json。
    • 设置请求的目标URL。
    • 设置请求的SSL配置,确保HTTPS请求的安全性。
  • 发送POST请求:
    • 使用QNetworkAccessManager对象imgManager发送一个POST请求,将处理好的postData作为请求体发送给百度AI平台进行人脸识别。

JSON解析(人脸信息提取与显示)

    imgManager = new QNetworkAccessManager(this);
    connect(imgManager,&QNetworkAccessManager::finished,this,&ImageRecognition::imgReply);
void ImageRecognition::imgReply(QNetworkReply *reply)
{
    if(reply->error() != QNetworkReply::NoError)
    {
        qDebug() << reply->errorString();
        return;
    }
    const QByteArray replyData = reply->readAll();
    qDebug() << replyData;
    QString faceInfo = "";
    QJsonParseError jsonErr;
    QJsonDocument doc = QJsonDocument::fromJson(replyData,&jsonErr);
    if(jsonErr.error == QJsonParseError::NoError)
    {
        QJsonObject obj = doc.object();

        if(obj.contains("timestamp"))
        {
            int tmpTime = obj.take("timestamp").toInt();
            if(tmpTime < latestTime)
            {
                return;
            }
            else
            {
                latestTime = tmpTime;
            }

        }
        if(obj.contains("result"))
        {
            QJsonObject resultObj = obj.take("result").toObject();
            //取出人脸列表
            if(resultObj.contains("face_list"))
            {
                QJsonArray faceList = resultObj.take("face_list").toArray();
                //取出第一张人脸信息
                QJsonObject faceObject = faceList.at(0).toObject();
                //取出人脸定位信息
                if(faceObject.contains("location"))
                {
                    QJsonObject locObj = faceObject.take("location").toObject();
                    if(locObj.contains("left"))
                    {
                        faceLeft = locObj.take("left").toDouble();
                    }
                    if(locObj.contains("top"))
                    {
                        faceTop = locObj.take("top").toDouble();
                    }
                    if(locObj.contains("width"))
                    {
                        faceWidth = locObj.take("width").toDouble();
                    }
                    if(locObj.contains("height"))
                    {
                        faceHeight = locObj.take("height").toDouble();
                    }
                }
                //取出年龄
                if(faceObject.contains("age"))
                {
                    age = faceObject.take("age").toDouble();
                    faceInfo.append("年龄:").append(QString::number(age)).append("\r\n");
                }
                //取出性别
                if(faceObject.contains("gender"))
                {
                    QJsonObject genderObj = faceObject.take("gender").toObject();
                    if(genderObj.contains("type"))
                    {
                        gender = genderObj.take("type").toString();
                        faceInfo.append("性别:").append(gender=="male"?"男":"女").append("\r\n");
                    }
                }
                //取出情绪
                if(faceObject.contains("emotion"))
                {
                    QJsonObject emotionObj = faceObject.take("emotion").toObject();
                    if(emotionObj.contains("type"))
                    {
                        emotion = emotionObj.take("type").toString();
                        faceInfo.append("情绪:").append(emotion).append("\r\n");
                    }
                }
                //取出颜值
                if(faceObject.contains("beauty"))
                {
                    beauty = faceObject.take("beauty").toDouble();
                    faceInfo.append("颜值:").append(QString::number(beauty)).append("\r\n");
                }
                //检测是否戴口罩
                if(faceObject.contains("mask"))
                {
                    QJsonObject maskObj = faceObject.take("mask").toObject();
                    if(maskObj.contains("type"))
                    {
                        mask = maskObj.take("type").toInt();
                        faceInfo.append("是否佩戴口罩:").append(mask?"戴口罩":"未佩戴口罩").append("\r\n");
                    }
                }
                //检测是否戴眼镜
                if(faceObject.contains("glasses"))
                {
                    QJsonObject glassesObj = faceObject.take("glasses").toObject();
                    if(glassesObj.contains("type"))
                    {
                        glasses = glassesObj.take("type").toString();
                        faceInfo.append("是否戴眼镜:");
                        if(glasses == "none")
                        {
                            faceInfo.append("未佩戴眼镜").append("\r\n");
                        }
                        else if(glasses == "common")
                        {
                            faceInfo.append("佩戴普通眼镜").append("\r\n");
                        }
                        else if(glasses == "sun")
                        {
                            faceInfo.append("佩戴墨镜").append("\r\n");
                        }
                    }
                }
                //取出活体检测值
                if(faceObject.contains("liveness"))
                {
                    QJsonObject livenessObj = faceObject.take("liveness").toObject();
                    if(livenessObj.contains("livemapscore"))
                    {
                        liveness = livenessObj.take("livemapscore").toDouble();
                        faceInfo.append("是真人的可能性:").append(QString::number(liveness));
                    }
                }
                ui->textBrowser->setText(faceInfo);
            }
        }
    }
    else
    {
        qDebug() << "JSON ERR:" << jsonErr.errorString();
    }
    reply->deleteLater();
}

这段代码定义了一个槽函数 imgReply,用于处理通过QNetworkAccessManager 发起的网络请求的回复。具体来说,它解析从服务器接收到的 JSON 数据,并从中提取有关人脸的各种信息,如年龄、性别、情绪、颜值、是否戴口罩、是否戴眼镜以及活体检测值等。

遇到的问题及其解决方案

1.在性能测试过程中,发现了由于大量图片处理导致的系统卡顿问题。通过详细的代码审查(白盒测试),定位到了问题根源在于Base64编码转换过程中消耗了过多的计算资源。为了解决这个问题,引入了多线程机制来分散处理任务,从而有效地减轻了单个线程的压力,并通过回归测试验证了方案的有效性。

什么是BASE64编码?
BASE64编码是一种用于将二进制数据转换为文本格式的编码方案。
主要目的是确保数据能够在只支持文本传输的环境中安全传输,同时保持数据的完整性和可读性。
BASE64编码的核心思想是将二进制数据转换为一组可打印ASCII字符,从而确保数据能够在各种系统之间可靠地传输。

为什么BASE64编码要占用大量CPU和内存资源?

  • 数据膨胀。BASE64编码过程中,每3个字节的二进制数据会被编码成4个字节的文本数据。这意味着原始数据量会增加大约33%。这种数据膨胀会导致更大的内存和CPU负担,尤其是在处理大量数据时。
  • 内存操作。在编码的过程中,需要将原始二进制数据读取到内存中,并进行一系列的操作。如:1.读取和缓存原始数据。需要将原始二进制数据加载到内存中。2.位运算。3.查找和替换,通过查表替换字符。4.将编码后的字符拼接成最终的字符串

2.在向HTTPS的网页发出请求的时候,需要在QT5中配置一下SSL/TLS,需要安装OpenSSL相应的版本库。
https://blog.csdn.net/gm2418/article/details/129865506?spm=1001.2014.3001.5506
这篇文章里面介绍了如何安装

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2107237.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

一个基于共享内存的内存数据库:2 设计

初级代码游戏的专栏介绍与文章目录-CSDN博客 我的github&#xff1a;codetoys&#xff0c;所有代码都将会位于ctfc库中。已经放入库中我会指出在库中的位置。 这些代码大部分以Linux为目标但部分代码是纯C的&#xff0c;可以在任何平台上使用。 源码指引&#xff1a;github源…

opencv轮廓近似,模板匹配

在图像处理领域&#xff0c;轮廓近似和模板匹配是两种非常关键的技术&#xff0c;它们广泛应用于计算机视觉、图像分析和图像识别等多个方面。本文将详细介绍如何使用OpenCV库进行轮廓近似和模板匹配&#xff0c;并给出具体的代码示例。 一、轮廓近似&#xff08;Contour Appr…

使用stripe进行在线支付、退款、订阅、取消订阅功能(uniapp+h5)

stripe官网:Stripe 登录 | 登录 Stripe 管理平台 然后在首页当中打开测试模式,使用测试的公钥跟私钥进行开发 测试卡号 4242 4242 4242 4242 1234 567 在线支付 stripe的在线支付有两种,第一种就是无代码,第二中就是使用api进行自定义,一般来说推荐第二种进行开发 无…

谁还只会用OBS?多场景录制试试这四款!

很多人在录屏的时候&#xff0c;尤其是打游戏的朋友&#xff0c;第一时间想到的都是OBS&#xff0c;其实除了这款工具&#xff0c;还有很多好用的第三方录屏工具&#xff0c;一样可以帮助我们录制出不卡顿的高清视频。今天&#xff0c;我们就来对比一下市面上四款热门的录屏软件…

echarts环形图

let dataValue[{value: 30,name: 桥梁,percent: 0.25,color: rgba(248,95,94,1),radius: [75%, 80%],center: [22%, 50%],},{value: 15,name: 隧道,percent: 0.25,color: rgba(243,185,71,1),radius: [65%, 70%],center: [22%, 50%],},{value: 18,name: 路基,percent: 0.25,col…

类似antdesign悬浮按钮上浮小动画【已验证,正常运行】

以下是基于vue2的完整代码&#xff0c;习惯用vue写了&#xff0c;如果是其他框架复制div和css就行 部分代码来自我搭建的GPT4o/Claude <template><div class"progress-container"><div class"circlenav-container"><div class"…

深度学习——引言

一、机器学习的关键因素 1.1 数据 每个数据集由一 个个样本组成&#xff0c;大多情况下&#xff0c;数据遵循独立同分布。通常每个样本由一组特征属性组成。 好的数据集 { 数据样本多 正确的数据 ( g a r b a g e i n , g a r b a g e o u t ) 好的数据集 \begin{cases} 数据…

通俗易懂理解Hive四种排序

前言 Hive的四种排序包括Sort By、Order By、Distribute By和Cluster By。有关这四种排序的区别&#xff0c;在大数据面试中可能会经常被问到&#xff0c;在我们很多人的实际应用中可能最常用的就是全局排序order by&#xff0c;因此对于其他几个排序理解并不准确&#xff0c;…

ardupilot开发 --- 炸酱面 篇

我的头可不是面头捏的 奥维互动地图ovital航点文件转Mission planner航点文件 奥维互动地图ovital航点文件转Mission planner航点文件 gcj02 转 wgs84 奥维互动地图&#xff1a;https://www.ovital.com 航线1.ovjsn 转换工具&#xff1a;https://github.com/huangyangl/geo_c…

Linux之grep命令

在文本文件中过滤&#xff0c;包含指定字符串的行 – grep [选项] 字符串 文本文件...• 常用命令选项 – -v&#xff0c;取反匹配 – -i&#xff0c;忽略大小写 ]# grep root /etc/passwd #包含root的行 ]# grep -v root /etc/passwd #不包含root ]# grep ROOT…

操作系统的功能及应用

操作系统介绍 操作系统&#xff08;Operating System, OS&#xff09;是计算机系统中不可或缺的核心软件&#xff0c;它负责管理和控制计算机硬件与软件资源&#xff0c;提供用户与计算机之间的交互界面。本文将详细探讨操作系统的功能、分类及其在现代社会中的应用。 操作系统…

通过redis-operator 来部署 Redis Cluster 集群

安装 Redis Operator 首先&#xff0c;需要安装 redis-operator。可以通过 Helm 或直接应用 YAML 文件来安装。 使用 Helm 安装&#xff1a; helm repo add ot-helm https://ot-container-kit.github.io/helm-charts/ helm install redis-operator ot-helm/redis-operator --…

2024 年的 Web3 游戏:演变、趋势和市场动态

Web3 游戏行业在经历了多年的快速发展和变革之后&#xff0c;正在2024年迎来全新的阶段。这个行业从最初的边玩边赚&#xff08;Play-to-Earn, P2E&#xff09;模式出发&#xff0c;如今正在向更为平衡的“边玩边赚”模式转型。这种转型不仅解决了早期 P2E 模式下存在的可持续性…

自动驾驶真正踏出迈向“用户”的第一步:IROS24新SOTA提出个性化的实例迁移模仿学习

导读&#xff1a; 本文针对自动驾驶规划任务&#xff0c;提出了一种基于实例的迁移模仿学习方法&#xff0c;通过预先训练的微调框架从专家域迁移专业知识&#xff0c;以解决用户域数据稀缺问题。实验结果显示&#xff0c;该方法能有效捕捉用户驾驶风格并实现具有竞争力的规划性…

select、poll、epoll的原理

目录 1.IO多路复用 2.select原理 3.poll原理 4.epoll原理 5.select、poll、epoll总结 6.epoll原理详解 6.1内核收包的过程 6.2进程调度时的阻塞 6.3再来看一下内核收网络数据的过程 6.4select的原理 6.5epoll的设计原理 6.6补充 6.7总结 1.IO多路复用 IO多路复用…

数据时代的领航者:首席数据官(CDO)如何影响城市治理?

随着2023年9月的到来&#xff0c;多个中国城市包括长沙和北京相继宣布引入首席数据官&#xff08;CDO&#xff09;机制&#xff0c;这标志着国家数据管理体系进入一个新纪元。 首席数据官的设立不仅是对传统数据管理方式的重大革新&#xff0c;也是增强数据战略意识和推动数据…

托勒密世界地图:现代地形图绘制的标杆诞生于公元2世纪

关注我们 - 数字罗塞塔计划 - 今天要为大家分享一幅公元150年左右的世界地图——托勒密世界地图&#xff0c;它是由古埃及的数学家、天文学家、地理学家及占星家劳狄乌斯托勒密绘制的。托勒密著有《天文学大成》、《地理学》和《占星四书》等著作&#xff0c;其中《地理学》一书…

从校园到产业园:数字媒体人才如何无缝对接产业需求?

在当今数字化时代&#xff0c;数字媒体产业蓬勃发展&#xff0c;对专业人才的需求日益旺盛。然而&#xff0c;如何实现从校园到产业园的无缝对接&#xff0c;成为关键问题。 为了实现无缝对接&#xff0c;一方面&#xff0c;学校应加强与产业的合作。邀请行业专家走进校园授课、…

免费分享一套SpringBoot+Vue驾校(预约)管理系统【论文+源码+SQL脚本】,帅呆了~~

大家好&#xff0c;我是java1234_小锋老师&#xff0c;看到一个不错的SpringBootVue驾校(预约)管理系统&#xff0c;分享下哈。 项目视频演示 【免费】SpringBootVue驾校(预约)管理系统 Java毕业设计_哔哩哔哩_bilibili 项目介绍 传统办法管理信息首先需要花费的时间比较多&…

OpenCV与Matplotlib:灰度图像

目录 读取灰度图像 代码解释 1. 导入库 2. 读取彩色图像 3. 转换为灰度图像 4. 将 BGR 图像转换为 RGB 格式 5. 创建子图并显示图像 总结&#xff1a; 整体代码 效果展示 衍生操作 1. 边缘检测 代码说明 整体代码 效果展示 2. 图像二值化 代码说明 整体代码 效…