Qt5实现可配置截图及基于百度OCR自动识别标题保存文件

  • Post author:
  • Post category:其他



需求

:当我在看视频学习的时候,需要屏幕指定区域的内容保存起来,采用常见的XX截图软件,你需要选择区域选择路径保存,把文件命名为有意义的名称,效率极其低下。作为一名计算机专业人员强调思考能力、动手能力和内功修炼层级,所以这点事情还是很简单的。

软件界面

软件界面

项目整体介绍


assist

: 主界面,主要包含交互逻辑,多屏支持。


uiservant

: 为界面提供辅助函数,其它逻辑处理


winhook

: 设置键盘鼠标钩子,取点使用


network

: 百度OCR API请求

采用集成工具QtCreator IDE设计师直接布局就OK了,Qt布局功能真心强大。

这里写图片描述

配置文件之Json解析与保存

Qt主要有 QJsonObject,QJsonArray,QJsonParseError, QJsonDocument 实现Json解析与格式化以及保存功能,采用组合的结构性模式。

[
    {
        "automatic_recognition_name_area": "360 184 220 30",
        "screenshot_doc_folder": "D:/CommonUser/Documents/",
        "screenshot_picture_area": "360 150 1140 810",
        "screenshot_picture_prefix_name": "线性代数",
        "subject": "可汗学院_线性代数"
    },
    {
        "automatic_recognition_name_area": "12 27 450 40",
        "screenshot_doc_folder": "D:/CommonUser/Documents",
        "screenshot_picture_area": "00 0 120 200",
        "screenshot_picture_prefix_name": "机器学习",
        "subject": "哈弗公开课_机器学习"
    }
]

对应 Json解析代码

QString Assist::loadConf()
{
    QString key;
    QJsonParseError err;
    QByteArray data;
    QJsonObject obj;
    QJsonObject::iterator it;

    QString OK = m_uiservant.fileReadAll(m_uiservant.getMapString("conf_file_path"), data);
    if(!m_uiservant.isOK(OK))
    {
        return OK;
    }

    QJsonDocument doc = QJsonDocument::fromJson(data,&err);
    if(err.error != QJsonParseError::NoError)
    {
        return "parse json error!";
    }

    if (!doc.isArray())
    {
        return "assist_conf.json error format";
    }

     QJsonArray ary = doc.array();

     for(int i=0;i< ary.size();++i)
     {
        stConf conf;
        obj = ary.at(i).toObject();
        key = "subject";
        if((it = obj.find(key))==obj.end())
        {
           continue;
        }
        conf.qstrSubject = it.value().toString();

        key = "screenshot_picture_prefix_name";
        if((it = obj.find(key))==obj.end())
        {
           continue;
        }
        conf.qstrPicName = it.value().toString();
        // 其它实现...
        m_lsConf.push_back(conf);
     }

     return m_uiservant.SUCCEEDED_STRING;
}

Json 保存

QString Assist::saveConf()
{
    QString qstrFilePath = m_uiservant.getMapString("conf_file_path");
    QJsonObject obj;
    QJsonDocument doc;
    QJsonArray ary;

    for(QList<stConf>::iterator it = m_lsConf.begin(); it!=m_lsConf.end(); ++it)
    {
        obj.insert("subject", it->qstrSubject);
        obj.insert("screenshot_doc_folder", it->qstrDocFolder);
        obj.insert("screenshot_picture_prefix_name", it->qstrPicName);
        obj.insert("automatic_recognition_name_area", it->qstrPicNameArea);
        obj.insert("screenshot_picture_area", it->qstrScrShotArea);
        ary.append(obj);
        obj = QJsonObject();
    }

    doc.setArray(ary);
    QString ret = m_uiservant.fileWrite(qstrFilePath, doc.toJson());
    if(!m_uiservant.isOK(ret))
    {
        return ret;
    }

    return m_uiservant.SUCCEEDED_STRING;
}

屏幕取点之Hook实现

之前做过一个自动化工具,录制屏幕取点采用C#实现,生成自定义脚本文件,获取当鼠标处于停在某处时,按下

Alt

键就生成点坐标。现在由于交互比较简单,采用比较三种交互方式,鼠标左键录制x,y坐标,右键录制w,h, 中间键停止录制。

怎么实现HOOK,参考非常棒的一篇文章:

QT中安装不使用dll的全局钩子


采用Windows底层API,

SetWindowsHookEx

实现,具体实现参照工程源码。

以下为获取点之后

HHOOK WinHook::keyHook=NULL;
HHOOK WinHook::mouseHook=NULL;
QWidget* WinHook::m_pWidget = NULL;

LRESULT CALLBACK keyProc(int,WPARAM,LPARAM lParam )
{
    KBDLLHOOKSTRUCT *pkbhs = (KBDLLHOOKSTRUCT *) lParam;
    if(pkbhs->vkCode==VK_F12)
    {
        WinHook::unHook();
    }
    return 0;
}

LRESULT CALLBACK mouseProc(int nCode, WPARAM wParam,LPARAM lParam)
{
    if (nCode == HC_ACTION)
    {
        LPMOUSEHOOKSTRUCT p = (MOUSEHOOKSTRUCT*)lParam;
        ((Assist*)WinHook::m_pWidget)->acceptPoint(wParam, p->pt.x, p->pt.y);
    }
    return 0;
}

void WinHook::setHook(QWidget* widget)
{
    m_pWidget = widget;
    keyHook =SetWindowsHookEx( WH_KEYBOARD_LL,keyProc,GetModuleHandle(NULL),0);
    mouseHook =SetWindowsHookEx( WH_MOUSE_LL,mouseProc,GetModuleHandle(NULL),0);
}
void WinHook::unHook()
{
    UnhookWindowsHookEx(keyHook);
    UnhookWindowsHookEx(mouseHook);

    keyHook = NULL;
    mouseHook = NULL;
    m_pWidget = NULL;
}

Qt 右下角弹窗之最简实现

当处于全屏时,需要提示采用保证是否成功,否则没有成功截图不是白忙活了吗?

    label = new QLabel;
    label->setWindowFlags(Qt::FramelessWindowHint|Qt::Popup);
    label->adjustSize();
    label->setWordWrap(true);
    label->setAlignment(Qt::AlignCenter);

注意此处

setWindowFlags


Qt::FramelessWindowHint|Qt::Popup

,无边框,Qt::Popup 保证全屏下不会被遮挡,这一点很重要!

void Assist::showTips(QString tips, bool bStatusShow, int stayLong)
{
    if(bStatusShow)
    {
        ui->status_label->setText(tips);
    }

    label->setText(tips);
    QRect rect = m_uiservant.getScreenRect();
    label->setMaximumWidth(200);
    label->setMaximumHeight(100);
    label->setMinimumWidth(200);
    label->setMinimumHeight(100);
    QFont font;
    font.setPointSize(12);
    label->setFont(font);
    label->setStyleSheet(QLatin1String("color:red;"));
    label->setGeometry(int(rect.width()-label->width()), int((rect.height()-label->height()-50)), label->width(), label->height());
    label->show();
    QTimer::singleShot(stayLong, this, SLOT(slotHideFinishedLabel()));
}

void Assist::slotHideFinishedLabel()
{
    label->hide();
}

设置位置,采用计时器实现,是不是很简单呢?

百度 OCR 之 Post 请求

Qt浏览器采用Chorm内核,对于网络请求自然不在话下,只不过Qt4与Qt5的迁移变化比较大,还是很痛苦的!前同事为了业务需要,竟然在一个软件里面插入两个Qt浏览器,真挺厉害的!

吐槽一下百度的官方API文档,

请求与返回的字段有些出入

,有些吐血。第一次使用它,感觉没有

阿里/讯飞

提供的服务体验棒。不过百度的 OCR 速度还可以,之前采用打码兔平台API(额,最近打码兔平台即将关闭了,辛亏只充5元人民币)

在获取图片文字主要做了三件事:

  1. 注册百度开发者账号,

    注册应用
  2. 获取上传图片需要的

    access_token
  3. 上传

    Base64

    +

    urlencode

    编码之后的图片。

在第3步操作上花了好几个小时折腾,终于看清并理解含义。

图像数据,base64编码后进行urlencode,
要求base64编码和urlencode后大小不超过4M,
最短边至少15px,最长边最大4096px,
支持jpg/png/bmp格式,当image字段存在时url字段失效

为什么需要

base64

编码之后还要进行

urlencode

Base64编码 使用的字符:
大小写字母各26个,
加上10个数字,
加号“+”,斜杠“/”,
一共64个字符,
等号=用来作为后缀用途。
其中的+, /, = 都是需要urlencode的,所以无法取代。

采用 python编码只需要几行代码就实现了

Python 获取 access_token

from urllib import parse,request
import ssl

host = 'https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=Bjm0X1hsLxrxsyA3hyuSUuke&client_secret=P6CBRGWnmTUSOboM0Dao5ZUI51SW8ODE'
req = request.Request(host,headers={'Content-Type':'application/json; charset=UTF-8'})
res = request.urlopen(req)
res = res.read()
print(res)

Python Post 获取识别结果

from urllib import parse,request
import ssl
import base64
host = 'https://aip.baidubce.com/rest/2.0/ocr/v1/general_basic/token?access_token=24.ae2a4c83a7a4778a2d12395b62b68099.2592000.1529261360.282335-11263768'
with open(r"D:\CommonUser\Documents\BookCode\QT\Test\screen_assist\ss.png", 'rb') as f:
    data = base64.b64encode(f.read())
textmod={'image': data,}
textmod = parse.urlencode(textmod).encode(encoding='utf-8')
req = request.Request(host,headers={'Content-Type':'application/x-www-form-urlencoded'},data=textmod,)
res = request.urlopen(req)
res = res.read()
print(res)

是不是很简答?此处使用我在百度注册的Id,请试一试就好,不然我不高兴就撤销这个应用。

Qt 实现 Post 获取识别结果

OK,试着用Qt实现 Post 请求,实现第三步的功能:

EMRequestTypeFlags NetWorker::fetchPictureOCR(QString access_token,
                                              QByteArray base64_img,
                                              QString language_type,
                                              QString detect_direction,
                                              QString detect_language,
                                              QString probability)
{
    QNetworkRequest request;

    QUrl url("https://aip.baidubce.com/rest/2.0/ocr/v1/general_basic");
    QUrlQuery query;
    query.addQueryItem("access_token",access_token);
    url.setQuery(query);
    request.setUrl(url);
    request.setRawHeader(QByteArray("Content-Type"), QByteArray("application/x-www-form-urlencoded"));
    base64_img = base64_img.toPercentEncoding();
    QByteArray postData;
    postData.append(QString("image=").toLatin1()+base64_img);
    postData.append(QString("&language_type="+language_type).toLatin1());
    postData.append(QString("&detect_direction="+detect_direction).toLatin1());
    postData.append(QString("&detect_language="+detect_language).toLatin1());
    postData.append(QString("&probability="+probability).toLatin1());

    QNetworkReply* reply = mNetmanager->post(request,postData);
    reply->ignoreSslErrors();
    connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(slotError(QNetworkReply::NetworkError)));
    connect(reply, SIGNAL(sslErrors(QList<QSslError>)), this, SLOT(slotSslErrors(QList<QSslError>)));
    mRequest[reply] = emRequestAccessOCR;

    return mRequest[reply];
}


application/x-www-form-urlencoded

格式是一种非常通用数据传输格式,

关键字=值&关键字=值

的形式传输数据。

实现base64的urlencod很简单,

base64_img.toPercentEncoding();

关键是一定要理解。

解决 Qt之https传输问题

https和ssl协议广泛应用,Qt并没有直接去实现这些协议,但是内部具有支持调用协议的接口,没有

libeay32.dll



ssleay32.dll

会爆出一大堆警告,百度API里面明确支持 https 协议,必须要有这两个文件。在本地一搜索,一大堆软件包含这两个dll,比如:QQ,WeChat, IQIYI… 等都含有。我直接采用 WeChat的这两个文件不好使,找到了

一篇文章

,请参照下载并放在应用程序所在目录。

思考

我一直使用Qt,主要是Qt支持跨平台,安卓,IOS,MAC, Linux, Window, WinCE,车载操作系统 …,而且还和Python完美融合, 具有一些 Python 的影子,事实上现代标准 C++ 也在向 Python学习,不过它花哨的语法,让我有些不爽。


亮点一

:我尝试在C++ 中融入脚本的思考,采用典型的词典代替其它变量声明:

void setMapString(QString qstrKey, QString qstrValue);
QString getMapString(QString qstrKey, QString qstrDefaultValue="");

我不希望一个类里面充满了大量的变量声明,统统塞入map里面,整个类就瘦了,干净很多。


亮点二

:代码分层,那些以简单为由,直接把逻辑与界面揉在一起的开发指导人员是在误导他人。

一个看似很简单的软件,其实是花了好几天来完成,做一件事情,不急不躁,坚持做完,你就胜利了。

加好友 &

工程下载

这里写图片描述

下一篇文章预告

当我看到旋转的风车,走动的唐老鸭,米老鼠,我就在想他们只不过是绘画出来的,我怎么能根据一幅画实现线条拆离以及动画的重组? 那么,你有更好的思路吗?欢迎加好友讨论 。



版权声明:本文为u010383605原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。