前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >基于qt的opencv实时图像处理框架FastCvLearn实战

基于qt的opencv实时图像处理框架FastCvLearn实战

作者头像
threeQing
发布2021-09-29 15:37:33
1.1K0
发布2021-09-29 15:37:33
举报

在油菜花开的春天,一个大叔带来了新玩具,FastCvLearn实战课程。

基于qt开发可以方便进行嵌入式移植,而实时图像处理在工业和工程上具有较高的效率。

对代码陌生的朋友,可以看看本文。如果熟悉了,可以直接文末到github仓库下载使用。

结果展示

基础知识回顾

首先回顾一下基础知识。方便复查这里贴出了百科截图。

uint8_t

unsigned char

BIN:0001 0000

NOT BIN:0001 0000

static_cast

手撕代码

算法函数控制方法

对于enum 类型,感觉很陌生,如何剖析?

代码语言:javascript
复制
    enum MASK_TYPE{
                   RECTANGLE = 0,
                   LANDMARKS,
                   GLASSES,
                   MUSTACHE,
                   MOUSE_NOSE,
                   MASK_COUNT,
    };
代码语言:javascript
复制

使用qDebug()函数即可。

代码语言:javascript
复制
代码语言:javascript
复制
qDebug()<<"CaptureThread::MASK_COUNT"<<CaptureThread::MASK_COUNT;
    qDebug()<<"CaptureThread::MOUSE_NOSE"<<CaptureThread::MOUSE_NOSE;
    qDebug()<<"CaptureThread::MUSTACHE"<<CaptureThread::MUSTACHE;
    qDebug()<<"CaptureThread::GLASSES"<<CaptureThread::GLASSES;
    qDebug()<<"CaptureThread::LANDMARKS"<<CaptureThread::LANDMARKS;
    qDebug()<<"CaptureThread::RECTANGLE"<<CaptureThread::RECTANGLE;
代码语言:javascript
复制
可以发现,原来其枚举值是整数。
代码语言:javascript
复制
CaptureThread::MASK_COUNT 5
CaptureThread::MOUSE_NOSE 4
CaptureThread::MUSTACHE 3
CaptureThread::GLASSES 2
CaptureThread::LANDMARKS 1
CaptureThread::RECTANGLE 0
代码语言:javascript
复制

那么,再结合先前我们百科得到的static_cast类型转换函数的定义,就不难理解下面这句代码了。对int类型i,进行类型转换为枚举类型CaptureThread::MASK_TYPE。

代码语言:javascript
复制
static_cast<CaptureThread::MASK_TYPE>(i)
代码语言:javascript
复制

综合上述知识,下面这个函数可以理解为将uint8_t masks_flag与uint8_t bit进行位运算。其可作为多个算法的开关函数。

代码语言:javascript
复制
代码语言:javascript
复制
uint8_t masks_flag;
masks_flag = 0;
    void updateMasksFlag(MASK_TYPE type, bool on_or_off) {
        uint8_t bit = 1 << type;
        if(on_or_off) {
            masks_flag |= bit;
        } else {
            masks_flag &= ~bit;
        }
    };

isMaskOn的位运算同理。

代码语言:javascript
复制
代码语言:javascript
复制
    bool isMaskOn(MASK_TYPE type) {return (masks_flag & (1 << type)) != 0; };

updateMasksFlag使用和isMaskOn使用,实现的效果就算给实时的视频帧添加多个特效独立的开关。

代码语言:javascript
复制
代码语言:javascript
复制
//updateMasksFlag使用
    QCheckBox *box = qobject_cast<QCheckBox*>(sender());
    for (int i = 0; i < CaptureThread::MASK_COUNT; i++){
        if (mask_checkboxes[i] == box) {
            capturer->updateMasksFlag(static_cast<CaptureThread::MASK_TYPE>(i), status != 0);
        }
    }
    
//isMaskOn使用
vector< vector<cv::Point2f> > shapes;
    if (mark_detector->fit(frame, faces, shapes)) {
        // draw facial land marks
        for (unsigned long i=0; i<faces.size(); i++) {
            if (isMaskOn(LANDMARKS)) {
                for(unsigned long k=0; k<shapes[i].size(); k++) {
                    cv::circle(frame, shapes[i][k], 2, color, cv::FILLED);
                     QString index = QString("%1").arg(k);
                     cv::putText(frame, index.toStdString(), shapes[i][k], cv::FONT_HERSHEY_SIMPLEX, 0.4, color, 2);
                }
            }
            if (isMaskOn(GLASSES))
                drawGlasses(frame, shapes[i]);
            if (isMaskOn(MUSTACHE))
                drawMustache(frame, shapes[i]);
            if (isMaskOn(MOUSE_NOSE))
                drawMouseNose(frame, shapes[i]);
        }
    }
代码语言:javascript
复制

算法函数运用

下面来看一下,特效装饰素材。

代码语言:javascript
复制
运用素材的方法:

在CaptureThread::run()线程里使用while函数,不停的调用分类器。注意帧数据的上锁和解锁,保证数据同步。

代码语言:javascript
复制
void CaptureThread::run() {
    running = true;
    cv::VideoCapture cap(cameraID);
    // cv::VideoCapture cap("/home/kdr2/Videos/WIN_20190123_20_14_56_Pro.mp4");
    cv::Mat tmp_frame;

    frame_width = cap.get(cv::CAP_PROP_FRAME_WIDTH);
    frame_height = cap.get(cv::CAP_PROP_FRAME_HEIGHT);

    const cv::String classifier_data = "haarcascades/haarcascade_frontalface_default.xml";
    classifier = new cv::CascadeClassifier(classifier_data);

    mark_detector = cv::face::createFacemarkLBF();
    QString model_data = QApplication::instance()->applicationDirPath() + "/data/lbfmodel.yaml";
    qDebug()<<"model_data"<<model_data;
    mark_detector->loadModel(model_data.toStdString());
    while(running) {
        cap >> tmp_frame;
        if (tmp_frame.empty()) {
            break;
        }

        if(masks_flag > 0)
            detectFaces(tmp_frame);

        if(taking_photo) {
            takePhoto(tmp_frame);
        }

        cvtColor(tmp_frame, tmp_frame, cv::COLOR_BGR2RGB);
        data_lock->lock();
        frame = tmp_frame;
        data_lock->unlock();
        emit frameCaptured(&frame);
    }
    cap.release();
    delete classifier;
    classifier = nullptr;
    running = false;
}
代码语言:javascript
复制

这里有个坑,cv::CascadeClassifier函数的输入需要是const cv::String格式的(即std::string),QString不行。

代码语言:javascript
复制
代码语言:javascript
复制
const cv::String classifier_data = "haarcascades/haarcascade_frontalface_default.xml";
    classifier = new cv::CascadeClassifier(classifier_data);

保存图像bug

本想借鉴如下toStdString()方式,发现运行不成功,toStdString(时间带冒号格式)方式的字符串,cv::imwrite调用后,写入图像无结果。

代码语言:javascript
复制
QString photo_path = "";
    cv::imwrite(photo_path.toStdString(), frame);

后来发现是字符串格式问题,测试发现这句bool writeResult = cv::imwrite(“a:a.jpg”, frame);不行。

走过一些弯路后,发现时间日期格式修改后即可,将"yyyy-MM-dd+HH:mm:ss"修改为"yyyy-MM-dd+HH-mm-ss"。其中不能有冒号:。

代码语言:javascript
复制

人脸识别的奥秘

接下来了解下,人脸识别的奥秘。

代码语言:javascript
复制
void CaptureThread::detectFaces(cv::Mat &frame)
{
    vector<cv::Rect> faces;
    cv::Mat gray_frame;
    cv::cvtColor(frame, gray_frame, cv::COLOR_BGR2GRAY);
    classifier->detectMultiScale(gray_frame, faces, 1.3, 5);

    cv::Scalar color = cv::Scalar(0, 0, 255); // red

    // draw the circumscribe rectangles
    if (isMaskOn(RECTANGLE)) {
        for(size_t i = 0; i < faces.size(); i++) {
            cv::rectangle(frame, faces[i], color, 1);
        }
    }

    vector< vector<cv::Point2f> > shapes;
    if (mark_detector->fit(frame, faces, shapes)) {
        // draw facial land marks
        for (unsigned long i=0; i<faces.size(); i++) {
            if (isMaskOn(LANDMARKS)) {
                for(unsigned long k=0; k<shapes[i].size(); k++) {
                    cv::circle(frame, shapes[i][k], 2, color, cv::FILLED);
                     QString index = QString("%1").arg(k);
                     cv::putText(frame, index.toStdString(), shapes[i][k], cv::FONT_HERSHEY_SIMPLEX, 0.4, color, 2);
                }
            }
            if (isMaskOn(GLASSES))
                drawGlasses(frame, shapes[i]);
            if (isMaskOn(MUSTACHE))
                drawMustache(frame, shapes[i]);
            if (isMaskOn(MOUSE_NOSE))
                drawMouseNose(frame, shapes[i]);
        }
    }
}
代码语言:javascript
复制

classifier->detectMultiScale(gray_frame, faces, 1.3, 5);用来创建多尺度的分类器,检测到的对象作为矩形列表返回。

代码语言:javascript
复制
代码语言:javascript
复制
detectMultiScale参数的理解可以参考:https://www.it610.com/article/1295047563740782592.htm
代码语言:javascript
复制

cv::face::createFacemarkLBF()用来创建局部二值特征(LBF)。

代码语言:javascript
复制
代码语言:javascript
复制
局部二值特征(LBF)具体可参考科普文:https://blog.csdn.net/qq_14845119/article/details/53575091
代码语言:javascript
复制
opencv官方文档介绍不多。

mark_detector->fit(frame, faces, shapes)用来画特征点。

素材加载的方法

这里QImage 与cv::Mat的相互转化值得借鉴。

代码语言:javascript
复制
void CaptureThread::loadOrnaments()
{
    QImage image;
    image.load(":/images/glasses.jpg");
    image = image.convertToFormat(QImage::Format_RGB888);
    glasses = cv::Mat(
        image.height(), image.width(), CV_8UC3,
        image.bits(), image.bytesPerLine()).clone();

    image.load(":/images/mustache.jpg");
    image = image.convertToFormat(QImage::Format_RGB888);
    mustache = cv::Mat(
        image.height(), image.width(), CV_8UC3,
        image.bits(), image.bytesPerLine()).clone();

    image.load(":/images/mouse-nose.jpg");
    image = image.convertToFormat(QImage::Format_RGB888);
    mouse_nose = cv::Mat(
        image.height(), image.width(), CV_8UC3,
        image.bits(), image.bytesPerLine()).clone();
}
代码语言:javascript
复制

drawGlasses(frame, shapes[i]);

该函数对角度敏感,角度变化大时,失效了。选取的特征点是根据landmarks排序后位置来手工定义的。比如:resize和rotate中的marks[]中的数字。

代码语言:javascript
复制
代码语言:javascript
复制
void CaptureThread::drawGlasses(cv::Mat &frame, vector<cv::Point2f> &marks)
{
    // resize
    cv::Mat ornament;
    double distance = cv::norm(marks[45] - marks[36]) * 1.5;
    cv::resize(glasses, ornament, cv::Size(0, 0), distance / glasses.cols, distance / glasses.cols, cv::INTER_NEAREST);

    // rotate
    double angle = -atan((marks[45].y - marks[36].y) / (marks[45].x - marks[36].x));
    cv::Point2f center = cv::Point(ornament.cols/2, ornament.rows/2);
    cv::Mat rotateMatrix = cv::getRotationMatrix2D(center, angle * 180 / 3.14, 1.0);

    cv::Mat rotated;
    cv::warpAffine(
        ornament, rotated, rotateMatrix, ornament.size(),
        cv::INTER_LINEAR, cv::BORDER_CONSTANT, cv::Scalar(255, 255, 255));

    // paint
    center = cv::Point((marks[45].x + marks[36].x) / 2, (marks[45].y + marks[36].y) / 2);
    cv::Rect rec(center.x - rotated.cols / 2, center.y - rotated.rows / 2, rotated.cols, rotated.rows);
    frame(rec) &= rotated;
}
代码语言:javascript
复制

cv::norm求解范数

一种非严密的解释就是,对应向量范数,向量空间中的向量都是有大小的,这个大小如何度量,就是用范数来度量的,不同的范数都可以来度量这个大小,就好比米和尺都可以来度量远近一样;对于矩阵范数,学过线性代数,我们知道,通过运算AX=B,可以将向量X变化为B,矩阵范数就是来度量这个变化大小的。

代码语言:javascript
复制
代码语言:javascript
复制
OpenCV:norm-范数求解函数:https://jingyan.baidu.com/article/454316ab3d46d4f7a7c03a89.html
代码语言:javascript
复制

cv::resize

要缩小图像,通常使用INTER_AREA插值效果最佳,而要放大图像,通常使用c :: INTER_CUBIC(速度慢)或INTER_LINEAR(速度更快,但看起来仍然可以)最好。

代码语言:javascript
复制
代码语言:javascript
复制
double angle = -atan((marks[45].y - marks[36].y) / (marks[45].x - marks[36].x));

反正切求角度

frame(rec) &= rotated; 局部ROI区域添加蒙版图。

Mat的框越界

凑近看landmarks的序号的时候,发现程序闪退,发现是这面这个问题:

代码语言:javascript
复制
代码语言:javascript
复制
OpenCV: terminate handler is called! The last OpenCV error is:
OpenCV(4.5.1) Error: Assertion failed (0 <= roi.x && 0 <= roi.width && roi.x + roi.width <= m.cols && 0 <= roi.y && 0 <= roi.height && roi.y + roi.height <= m.rows) in cv::Mat::Mat, file D:\work\OpenCV451Sln\opencv-4.5.1\modules\core\src\matrix.cpp, line 811

查看了下面的代码参考后,认定是rec的问题:

代码语言:javascript
复制
https://github.com/Huang9495/roi-
代码语言:javascript
复制
解决方法:
代码语言:javascript
复制
代码语言:javascript
复制
//    cv::Rect rec(center.x - rotated.cols / 2, center.y - rotated.rows / 2, rotated.cols, rotated.rows);
    cv::Rect rec((center.x - rotated.cols / 2)>0?(center.x - rotated.cols / 2):0,
                 (center.y - rotated.rows / 2)>0?(center.y - rotated.rows / 2):0,
                 rotated.cols>0?rotated.cols:0,
                 rotated.rows>0?rotated.rows:0);
    frame(rec) &= rotated;

该方式可以解决部分素材的越界问题。可是,胡子和花猫的素材,凑近后仍有程序自动退出问题。

解决方向是对的,后续有需要再优化吧。

886

参考链接:

代码语言:javascript
复制
1: [Qt-5-and-OpenCV-4-Computer-Vision-Projects.](https://github.com/PacktPublishing/Qt-5-and-OpenCV-4-Computer-Vision-Projects)
2: [FastCvLearn代码仓库,欢迎stars](https://github.com/pengguanjun/FastCvLearn)
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-09-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 机器视觉那些事儿 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 结果展示
  • 基础知识回顾
  • 手撕代码
相关产品与服务
人脸识别
腾讯云神图·人脸识别(Face Recognition)基于腾讯优图强大的面部分析技术,提供包括人脸检测与分析、比对、搜索、验证、五官定位、活体检测等多种功能,为开发者和企业提供高性能高可用的人脸识别服务。 可应用于在线娱乐、在线身份认证等多种应用场景,充分满足各行业客户的人脸属性识别及用户身份确认等需求。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档