TianSong

TianSong 查看完整档案

南京编辑菏泽学院  |  自动化 编辑中国电子  |  嵌入式软件开发 编辑 github.com/cocowts 编辑
编辑

阿里山神木的种子在3000年前已经埋下,今天不过是看到当年注定的结果,为了未来的自己,今天就埋下一颗好种子吧

个人动态

TianSong 发布了文章 · 4月11日

简单考勤

Github 源码&可执行文件

百度网盘可执行文件(提取码 :0zj5)

简述

简单考勤是一款小巧、便捷、免费的人脸识别考勤管理系统(仅需一台win电脑+有线或网络摄像头)
支持活体监测、语音提示、考勤记录导出等功能,对于五百人以下团体拥有优秀的管理效率

此应用由技术爱好者个人开发,欢迎问题及改进建议反馈, 邮箱:1508539502@qq.com
注:目前仅支持 64 位操作系统系统 

主要功能说明

登录页

只有当用户登陆后,才可进行人员管理、系统配置、关闭等操作(当前版本用户名密码默认都为 admin

  • 支持账号密码合法性验证
  • 支持随机验证码验证

登录

首页

画面信息的实时预览

  • 支持温馨语轮播提示
  • 支持实时时间提示
  • 支持总注册人员数量提示
  • 支持人脸框标定提示
  • 支持识别成功后人员姓名提示(伴随语音提示)

首页

视频流

注册

注册页

  • 支持单人及批量人员注册
  • 支持注册记录预览(在查询注册失败时尤为重要)

    注册头像格式要求:姓名.jpeg (小明.jpeg)
    

注册

统计页

  • 支持注册人员头像预览
  • 支持注册人员详细信息查看
  • 支持人员姓名模糊查找
  • 支持人员删除
  • 支持上下班考勤时间设定
  • 支持人员考勤记录导出

统计

考勤记录

设置页

  • 支持相机参数设置
  • 支持人脸识别阈值设置
  • 支持人脸识别引擎 SDK 管理
  • 支持开机自启动设置

设置

其它功能说明

可编辑的提示语说明

页面轮播

安装路径下可编辑的 hint.txt 文件,每次启动软件后生效

hint

语音提示

安装路径下可编辑的 voice.txt 文件,每次启动软件后生效

voice

初始化流程

  • 登录简单考勤客户端
  • 在“设置页“进行 SDK 设置
  • 关闭客户端,重启登录
  • 在”设置页“开启相机

人脸识别 SDK 的相关说明

  • 客户端使用虹软人脸识别引擎,需要在虹软官网申请 appId 和 sdkKey
  • 每个注册账户申请一次可免费使用一年
  • 虹软要求,每一台新电脑初次使用sdk时,需要联网认证,之后再无联网要求

appId 和 appKey 获取步骤

image.png

  • 点击”新建应用“

image.png

  • 完善内容后点击”立即创建“

image.png

  • 点击 ”添加SDK“

image.png

  • 点击 ”人脸识别“

image.png

  • 按图选择相应内容 (一定不可填错)后勾选”我同意“,点击”确认“

image.png

  • 成功申请到 appId 和 sdkKey

image.png

  • 将 appId 和 sdkKey 分别复制到简单考勤系统 ”设置页面“,重启系统后生效

image.png

查看原文

赞 1 收藏 1 评论 0

TianSong 发布了文章 · 3月30日

【C++内存管理】1_内存分配的每个层面

C++ 应用程序,使用 memory 的途径

image.png

C++ memory primitives

分配释放类属可否重载
malloc()free()C 函数不可
newdeleteC++ 表达式不可
::operator new()::operator delete()C++ 函数
allocator<T>::allocate()allocator<T>::deallocateC++ 标准库可自由设计并予以搭配任何容器
void *p1 = malloc(512); // 512 bytes
free(p1)
complex<int>* p2 = new complex<int>;    // one object
delete p2;
void *p3 = ::operator new(512); // 512 bytes
::operator delete(p3);
// 以下使用 C++ 标准库提供的 allocators
// 其接口虽有标准规格,但实现厂商并未完全遵守;下面三种形式略异
#ifdef _MSV_VER
    // 以下两个函数都是 no-static, 要通过 object 调用。
    // 分配 3 个 ints
    int *p4 = allocator<int>().allocate(3, (int*)0);    // 对应标准库分配器的第二个参数
    allocator<int>().deallocate(p4, 3);
#endif

#ifdef __BORLANDC__
    // 以下两个函数都是 no-static, 要通过 object 调用。
    // 分配 5 个 ints
    int *p4 = allocator<int>().allocate(5); // 同样包含第二个参数,但声明处有默认值,因此调用处可不写
    allocator<int>().deallocate(p4, 5);
#endif

#ifdef __GNUC__ // 早期版本的实现, 2.9
    // 以下两个啊含糊都是 static, 可通过全名调用。
    // 分配 512 bytes
    void *p4 = alloc::allocate(512);
    alloc::deallocate(p4, 512);
#endif
#ifdef __GNUC__ // 现代版本的实现, 4.9
    // 以下两个函数都是 no-static,要通过 object 调用。
    // 分配 7 个 ints
    void *p4 = allocator<int>().allocate(7);
    allocator<int>().deallocate((int*)p4, 7);
    
    // 以下两个函数都是 no-static,要通过 object 调用。
    // 分配 9 个 ints
    void *p4 = __gnu_cxx::pool_alloc<int>().allocate(9);
    __gnu_cxx::pool_alloc<int>.deallocate((int*)p4, 9);
#endif

new expression (new 背后的行为)

Complex *pc = new Complex(1, 2);

编译器转换为 ==>>

Complex *pc;
try {
    /* 1 */ void mem = operator new (sizeof(Complex));  // allocate 申请内存空间
    /* 2 */ pc = static_cast<Complex*>(mem);            // cast 类型转换
    /* 3 */ pc->Complex::Complex(1, 2);                 // construct 调用构造函数
    // 注意:只有编译器才可以像上面那样直接呼叫 ctor
}
catch (std::bad_alloc) {
    // 若 allocation 失败,就不执行 constructor 
}
注:
  • 申请内存可能会失败,因此引入 try...catch...
  • new 做两个动作

    • 申请内存
    • 调用构造函数
  • 欲直接调用 ctor, 可调用 placement new, new(p)Complex(1, 2)
// ...\vc98\crt\src\newop2.cpp (其中一个实现版本)
void *operator(size_t size, const std::nothrow_t &_THROW0())
{
    // try to allocate size bytes
    void *p;
    while ((p = malloc(size)) == 0)
    {
        // buy more memory or return null pointer
        __TRY_BEGIN
        if (_callnewh(size) == 0) break;
        _CATCH(std::bad_alloc)  return (0);
        _CATCH_END
    }
}
注:
  • std::nothrow_t 结构用作 new 运算符的函数参数,指示该函数应返回空指针以报告分配失败,而不是引发异常(struct std::nothrow_t{})
  • 当内存申请失败,_callnewh 调用 typedef void (*new_handler)(); new_handler set_new_handler(new_handler p) throw() 设置的函数,使得我们有机会释放掉我们认为可以释放的内存空间

delete expression (delete 背后的行为)

Complex *pc = new Complex(1, 2);
delete pc;

编译器转换为 ==>>

pc->~Complex();         // 调用析构函数
operator delete (pc);   // 释放内存
注:delete 的两个动作
  • 调用析构函数
  • 释放内存
// ...\vc98\crt\src\delop.cpp (其中一个实现版本)
void __cdelc operator delete(void *p) __THROW0()
{
    // free an allocated object
    free(p);
}

Ctor(构造函数) & Dtor(析构函数) 直接调用

#include <iostream> 

using namespace std;

class A {
public:
    int id;
    
    A() : id(0) {
        cout << "default ctor. this=" << this << " id=" << id << endl;
    }
    
    A(int i) : id(i) {
          cout << "ctor. this=" << this << " id=" << id << endl;
    }    
    
    ~A() {
         cout << "dtor. this=" << this << " id=" << id << endl; 
    }
};

void test_1()
{
    cout << endl << "test_1" << endl;
    
    A *pA = new A(1);
    
    cout << pA->id << endl;

    delete pA;
}

// simulate new
void test_2()
{
    cout << endl << "test_2" << endl;
    
    void *p = ::operator new(sizeof(A));    
    
    cout << "p=" << p << endl;
    
    A *pA = static_cast<A*>(p);
    
    cout << pA->id << endl;
    
    pA->~A();
    
    ::operator delete(pA);
}

void test_3()
{
    cout << endl << "test_3" << endl;
    
    A *pA = new A(3);
    
    cout << pA->id << endl;
    
    // pA->A::A(3);    // [Error] cannot call constructor 'A::A' directly
    
    // A::A(5);        // [Error] cannot call constructor 'A::A' directly

    pA->~A();        // 编译无错误, 析构函数被调用 

    delete pA;
}

int main()
{
    test_1();
    
    test_2();
    
    test_3();
    
    return 0;    
}

输出:[编译器 gnu 4.9,2]

test_1      // 构造、析构函数被调用,一切正常
ctor. this=0x781510 id=1
1
dtor. this=0x781510 id=1

test_2      // 构造函数未被调用
p=0x781510
7870992     // id 为内存中的随机值
dtor. this=0x781510 id=7870992  // 析构函数可被直接调用,但存在风险!!

test_3
ctor. this=0x781510 id=3        // 构造函数被调用
3
dtor. this=0x781510 id=3        // 析构函数被直接调用,但存在风险!!
dtor. this=0x781510 id=3        // 析构函数被 delete 时调用
注:
  • 语法上构造函数不可被直接调用;编译器发出错误
  • 语法上析构函数可被直接调用;编译通过;但不可以这样使用,比如析构函数中需要释放系统资源时,析构函被多次调用,资源也将被释放多次,行为未定义
  • 实际使用时,构造函数、析构函数都不应该直接调用
查看原文

赞 0 收藏 0 评论 4

TianSong 赞了回答 · 3月14日

解决C++使用FindWindow (windows.h) 为什么不能识别中文?

在 C++11标准增加了一种新的字符串定义方式原始字符串文字(raw string literal)

auto s0 = "hello";    // const char*
auto s1 = u8"hello";// const char*, encoded as UTF-8
auto s2 = L"hello"; // const wchar_t*
auto s3 = u"hello"; // const char16_t*, encoded as UTF-16
auto s4 = U"hello"; // const char32_t*, encoded as UTF-32

把你的中文字符串前面加上u,应该就是你需要的,然后别忘了在编译的时候加上 -std=c++11

关注 2 回答 1

TianSong 回答了问题 · 3月7日

解决大端和小端显示

注意: if 里面写的赋值操作 = 而不是比较操作 ==!!!

if(a=1);

补充:

  • 老式编译器建议把 常量值放在 == 左侧,防止误写
  • 新式编译器一般会对上述代码中情况发出编译警告,为了可读性,可以把 变量放在 == 右侧
  • 警告即错误(除非你十分清楚)

关注 3 回答 1

TianSong 回答了问题 · 3月6日

解决字符指针和字符数组

  • 数组声明是编译器自动分配一片连续的内存空间,空间名为数组名
  • 指针声明是只分配了用于容纳地址值的 4 字节空间

image.png


补充:

  • 关于 chcj, 在栈中分配空间用于存储实际的数据 (如果有全局属性,则存储在 data 段)
  • 可进行 ch[0] = 'd' 的赋值操作。
  • 因为数组是一片连续的内存空间存储数据,所以 chcj 地址不同,只是初始存储值相同

  • 关于 pj 在栈中分配空间存储其它地址值 (如果有全局属性,则存储在 data 段)
  • 可进行 p = other_addr 的赋值操作;但 *p = 'd'(等价于p[0]='d') 将引发运行时异常
  • &p, &j 本身地址不同,但指向可以相同。
  • 初始化时编译器做的内存优化,"asdfg"只需要一份,并且存储在'rodata' 段,因此 pj 指向相同

关注 2 回答 1

TianSong 回答了问题 · 3月3日

解决关于C语言 1 << 8 的疑惑

  • 可以理解为一种模板,主要为了便于书写、阅读与理解;
  • 在嵌入式开发中常用,在涉及硬件操作的库中常见(C\C++)。

比如,控制单片机 IO_P2 输出高,而不影响其它引脚位:

unsigned int val = *(register_p2_addr); // 获取当前状态值
val = val | (1 << 2);                   // 仅修改对应第 2 位
*(register_p2_addr) = val;              // 修改后的值写回

简写等价于 ==> 

*(register_p2_addr) |= (1 << 2);        

从上可以看出,比自己手动计算,可以很容易的找到某一位,因此更容易书写与理解。甚至理解或封装成一种模板。

void setPinHeight(int pin_num)
{
    *(register_p2_addr) |= (1 << pin_num);  
}

尤其底层硬件驱动程序开发时,官方数据手册中有无数无数无数个寄存器,上面的位操作是必须要使用的。厂商的官方驱动也采用这种方式。

关注 3 回答 3

TianSong 发布了文章 · 3月1日

【Qt】opencv调用摄像头的简单应用

最近需要使用opencv,就简单写了一个测试示例并记录下来。
其中 QCamera 等相关的类同样可以实现当前功能。

界面展示.png

image.png

仓库

功能

  • 支持有线或IP摄像头连接
  • 支持视频流播放
  • 支持多分辨率设置
  • 支持视频录制
  • 支持拍照
  • 视频抽帧、拍照、录制等由独单线程处理

测试可用的视频流

CCTV1  高清 http://ivi.bupt.edu.cn/hls/cctv1hd.m3u8
CCTV3  高清 http://ivi.bupt.edu.cn/hls/cctv3hd.m3u8
CCTV5+ 高清 http://ivi.bupt.edu.cn/hls/cctv5phd.m3u8
CCTV6  高清 http://ivi.bupt.edu.cn/hls/cctv6hd.m3u8
camara.h
#ifndef CAMARA_H
#define CAMARA_H

#include <QImage>
#include <QObject>
#include <QThread>
#include <QTimer>

#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>

Q_DECLARE_METATYPE(QImage);

/*
 *@brief Camara 在单独线程中完成视频抽帧、拍照及录像功能
 *@brief open、close 等是耗时操作,请保证在 Camara 线程时间中完成调用 (可考虑异步槽函数、QMetaObject::invokeMethod)
 */

class Camara : public QObject
{
    Q_OBJECT

public:
    Camara();
    ~Camara();

    bool isOpened()        const;
    bool isTakevideoed()   const;
    QString getSavePath()  const;
    QSize getResolutions() const;
    bool isTakeVideo()     const;

signals:
    void updateImage(QImage);
    void statusChanged(bool isOpen);

public slots:
    void openCamara(const QString &url);
    void openCamara(int index);
    void closeCamara();
    void takePicture();
    void takeVideo();
    void setSavePath(const QString &path);
    void setResolutions(const QSize &size);

private slots:
    void tbegin();
    void tend();
    void captureCamara();

private:
    QString m_savepath;
    QAtomicInteger<bool> m_isTakepicture = false;
    QAtomicInteger<bool> m_isTakevideo   = false;
    QAtomicInteger<bool> m_isflip        = false;

    QScopedPointer<cv::VideoCapture> m_capture;
    QScopedPointer<cv::VideoWriter>  m_writer;

    QTimer  *m_timer = nullptr;
    QThread m_thread;
};

#endif // CAMARA_H
#include "camara.h"

#include <QDateTime>

Camara::Camara()
{
    moveToThread(&m_thread);

    connect(&m_thread, &QThread::started,  this, &Camara::tbegin);
    connect(&m_thread, &QThread::finished, this, &Camara::tend);

    m_thread.start(QThread::HighPriority);
}

Camara::~Camara()
{
    m_thread.quit();
    m_thread.wait();
}

void Camara::tbegin()
{
    m_capture.reset(new cv::VideoCapture);
    m_writer.reset(new cv::VideoWriter);
    m_timer = new QTimer(this);

    m_timer->setTimerType(Qt::PreciseTimer);

    connect(m_timer, &QTimer::timeout, this, &Camara::captureCamara);
}

void Camara::tend()
{
    closeCamara();
}

void Camara::openCamara(const QString &url)
{
    if (!m_capture->isOpened() && m_capture->open(url.toLatin1().data()))
    {
        m_isflip = false;

        m_timer->start(33);

        emit statusChanged(true);
    }
    else
    {
       emit statusChanged(false);
    }
}

void Camara::openCamara(int index)
{
    if (!m_capture->isOpened() && m_capture->open(index))
    {
        m_isflip = true;

        m_timer->start(33);

        emit statusChanged(true);
    }
    else
    {
        emit statusChanged(false);
    }
}

void Camara::closeCamara()
{
    m_timer->stop();

    if (m_writer->isOpened())
        m_writer->release();

    if (m_capture->isOpened())
        m_capture->release();
}

void Camara::captureCamara()
{
    cv::Mat originalframe;
    cv::Mat flipframe;

    *m_capture >> originalframe;

    if (m_isflip)
        cv::flip(originalframe, flipframe, 1);
    else
        flipframe = originalframe;

    QImage img = QImage(flipframe.data, flipframe.cols, flipframe.rows, QImage::Format_RGB888).rgbSwapped();

    if (!img.isNull())
    {
        if (m_isTakepicture)
        {
            m_isTakepicture = !m_isTakepicture;

            QString name = m_savepath + QDateTime::currentDateTime().toString("yyyy-MM-hh hh_mm_ss") + ".jpeg";

            img.save(name, "jpeg");
        }

        if (m_isTakevideo)
        {
            *m_writer << flipframe;
        }

        updateImage(img);
    }

    originalframe.release();
    flipframe.release();
}

void Camara::takePicture()
{
    m_isTakepicture = true;
}

void Camara::takeVideo()
{
    if (!m_isTakevideo)
    {
        QString name =  m_savepath + QDateTime::currentDateTime().toString("yyyy-MM-hh hh_mm_ss") + ".avi";

        if (m_writer->open(name.toLatin1().data(), cv::VideoWriter::fourcc('M', 'J', 'P', 'G'), 30.0, cv::Size(m_capture->get(cv::CAP_PROP_FRAME_WIDTH), m_capture->get(cv::CAP_PROP_FRAME_HEIGHT)), true))
        {
            m_isTakevideo = true;
        }
    }
    else
    {
        m_isTakevideo = false;

        m_writer->release();
    }
}

void Camara::setSavePath(const QString &path)
{
    m_savepath = path + '/';
}

void Camara::setResolutions(const QSize &size)
{
    if (m_capture->isOpened())
    {
        m_capture->set(cv::CAP_PROP_FRAME_WIDTH, size.width());
        m_capture->set(cv::CAP_PROP_FRAME_HEIGHT, size.height());
    }
}

QString Camara::getSavePath() const
{
    return m_savepath;
}

bool Camara::isOpened() const
{
    return m_capture->isOpened();
}

bool Camara::isTakevideoed() const
{
    return m_isTakevideo;
}

QSize Camara::getResolutions()  const
{
    QSize ret;

    ret.setWidth(m_capture->get(cv::CAP_PROP_FRAME_WIDTH));
    ret.setHeight(m_capture->get(cv::CAP_PROP_FRAME_HEIGHT));

    return ret;
}

bool Camara::isTakeVideo() const
{
    return m_writer->isOpened();
}
查看原文

赞 0 收藏 0 评论 0

TianSong 回答了问题 · 3月1日

解决c++ 怎样重载<<操作符才能连续使用?

#include <iostream>

class myOutText{
public:
    myOutText &operator << (auto s) {   // 返回自身引用以支持连续操作
        std::cout << s;
        return *this;
    }

    void CR() {
        putchar('\n');
    }
};

int main(void)
{
    myOutText cout; 

    cout << "string<<";
    cout.CR(); 
    cout << "1<<2<<\n";
    cout << 1 << 2 << '\n';
        
    return 0;
}

输出:

book@100ask:~/Desktop$ g++ test.cpp 
book@100ask:~/Desktop$ ./a.out 
string<<
1<<2<<
12

关注 2 回答 2

TianSong 回答了问题 · 2月28日

关于c++智能指针源码实现的疑问

我的理解是:
if (__tmp != _M_pi) 是为了避免自赋值,自赋值又发生在形参是本身类型时。

关注 2 回答 1

TianSong 回答了问题 · 2月18日

解决C++模板中,函数的具体类型是什么?

linked_list<int>* merge_two_linkedlists_asc<int, bool (*)(int, int)>(linked_list<int>* lists1, linked_list<int>* lists2, compare_int)
  1. T 可能是各种各样的自定义类型,因此需要调用者提供链表合并时元素T的比较方式。
  2. Compare &_cmp 一个可调用对象,可能的类型:

    • 函数原型: bool cmp(int, int) {}
    • 函数指针: bool (*cmp)(int, int);
    • lambda 表达式: auto cmp = &->bool{...}
    • 重载了()运算符的类: class cmp { bool operator(int, int){...} };
    • std::bind
    • std::function
  3. 使用”引用“是避免 _cmp 实参是非函数、指针而是闭包、自定义对象的低效拷贝。

关注 4 回答 2

认证与成就

  • 获得 140 次点赞
  • 获得 7 枚徽章 获得 0 枚金徽章, 获得 1 枚银徽章, 获得 6 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

  • 简单桌面

    简单桌面是一款小巧便捷的桌面背景管理软件. - 支持单静态图片及多静态图片轮播 - 支持GIF动画背景 - 支持视频背景 - 支持背景自定义标签 - 支持任务栏管理

注册于 2018-11-19
个人主页被 6.9k 人浏览