一、项目背景
在日常工作和生活中,我们常常需要处理大量的照片文件,这些照片中可能包含有用的文字信息。手动识别这些文字并对相应的照片进行重命名是一项繁琐且容易出错的工作。为了解决这一问题,本项目旨在开发一个基于QT和腾讯OCR(光学字符识别)技术的应用程序,实现批量识别照片中的文字并将识别出的文字作为照片的新文件名。
通过本项目,用户可以:
QT提供了丰富的UI组件和灵活的布局方式,适合构建功能强大且用户友好的桌面应用。以下是该应用的主要界面设计元素:
1. 主窗口布局
QNetworkAccessManager
)、JSON解析(如nlohmann/json
或QT自带的JSON解析器)、图像处理(如OpenCV
)等。PhotoOCRRenamer/
├── main.cpp
├── mainwindow.ui
├── mainwindow.cpp
├── mainwindow.h
├── ocrmanager.cpp
├── ocrmanager.h
├── photomanager.cpp
├── photomanager.h
├── utils.cpp
├── utils.h
├── resources.qrc
└── PhotoOCRRenamer.pro
main.cpp
cpp#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
mainwindow.h
cpp#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include "photomanager.h"
#include "ocrmanager.h"
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void on_selectFolderButton_clicked();
void on_startButton_clicked();
void updateProgress(int value);
void logMessage(const QString &msg);
void ocrFinished(const QString &photoPath, const QString &ocrText);
private:
Ui::MainWindow *ui;
PhotoManager *photoManager;
OcrManager *ocrManager;
};
#endif // MAINWINDOW_H
mainwindow.cpp
cpp#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QFileDialog>
#include <QMessageBox>
#include <QDir>
#include <QFileInfo>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
photoManager = new PhotoManager(this);
ocrManager = new OcrManager(this);
// 连接信号与槽
connect(ui->selectFolderButton, &QPushButton::clicked, this, &MainWindow::on_selectFolderButton_clicked);
connect(ui->startButton, &QPushButton::clicked, this, &MainWindow::on_startButton_clicked);
connect(ocrManager, &OcrManager::progressUpdated, this, &MainWindow::updateProgress);
connect(ocrManager, &OcrManager::logMessage, this, &MainWindow::logMessage);
connect(ocrManager, &OcrManager::ocrFinished, this, &MainWindow::ocrFinished);
// 设置初始状态
ui->startButton->setEnabled(false);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::on_selectFolderButton_clicked()
{
QString folderPath = QFileDialog::getExistingDirectory(this, "选择照片文件夹");
if (!folderPath.isEmpty()) {
photoManager->loadPhotos(folderPath);
ui->startButton->setEnabled(photoManager->photoCount() > 0);
ui->statusBar->showMessage("已加载 " + QString::number(photoManager->photoCount()) + " 张照片");
}
}
void MainWindow::on_startButton_clicked()
{
if (photoManager->photoCount() == 0) {
QMessageBox::warning(this, "警告", "请先选择照片文件夹!");
return;
}
// 配置OCR参数(如API密钥等)
ocrManager->setApiKey("YOUR_TENCENT_CLOUD_API_KEY");
ocrManager->setSecretKey("YOUR_TENCENT_CLOUD_SECRET_KEY");
// 开始OCR处理
ocrManager->startProcessing(photoManager->photos());
}
void MainWindow::updateProgress(int value)
{
ui->progressBar->setValue(value);
}
void MainWindow::logMessage(const QString &msg)
{
ui->logTextEdit->append(msg);
}
void MainWindow::ocrFinished(const QString &photoPath, const QString &ocrText)
{
// 重命名照片
QFileInfo fileInfo(photoPath);
QString newFileName = sanitizeFileName(ocrText) + QFileInfo(photoPath).suffix();
QString newFilePath = fileInfo.dir().filePath(newFileName);
int counter = 1;
while (QFile::exists(newFilePath)) {
newFileName = sanitizeFileName(ocrText) + "_" + QString::number(counter) + QFileInfo(photoPath).suffix();
newFilePath = fileInfo.dir().filePath(newFileName);
counter++;
}
bool success = QFile::rename(photoPath, newFilePath);
if (success) {
logMessage("重命名成功: " + photoPath + " -> " + newFilePath);
} else {
logMessage("重命名失败: " + photoPath);
}
// 更新进度
int current = photoManager->processedCount() + 1;
int total = photoManager->photoCount();
updateProgress((current * 100) / total);
// 检查是否所有照片都已处理
if (current == total) {
logMessage("所有照片处理完成!");
}
}
QString MainWindow::sanitizeFileName(const QString &text)
{
QString result = text;
// 移除非法字符
QRegularExpression regex("[<>:\"/\\\\|?*]");
result.remove(regex);
// 去除前后空格并替换内部空格为下划线
result = result.trimmed().replace(" ", "_");
return result;
}
photomanager.h
cpp#ifndef PHOTOMANAGER_H
#define PHOTOMANAGER_H
#include <QObject>
#include <QStringList>
#include <QFileInfo>
class PhotoManager : public QObject
{
Q_OBJECT
public:
explicit PhotoManager(QObject *parent = nullptr);
void loadPhotos(const QString &folderPath);
int photoCount() const;
QList<QString> photos() const;
int processedCount() const;
signals:
void photoLoaded(const QString &photoPath);
public slots:
void markProcessed(const QString &photoPath);
private:
QList<QString> m_photos;
QList<QString> m_processedPhotos;
};
#endif // PHOTOMANAGER_H
photomanager.cpp
cpp#include "photomanager.h"
#include <QDir>
#include <QFileInfo>
PhotoManager::PhotoManager(QObject *parent) : QObject(parent)
{
}
void PhotoManager::loadPhotos(const QString &folderPath)
{
QDir dir(folderPath);
QStringList filters;
filters << "*.jpg" << "*.jpeg" << "*.png" << "*.bmp" << "*.tiff";
dir.setNameFilters(filters);
QFileInfoList list = dir.entryInfoList(QDir::Files);
m_photos.clear();
for(auto &info : list){
m_photos.append(info.absoluteFilePath());
emit photoLoaded(info.absoluteFilePath());
}
}
int PhotoManager::photoCount() const
{
return m_photos.size();
}
QList<QString> PhotoManager::photos() const
{
return m_photos;
}
int PhotoManager::processedCount() const
{
return m_processedPhotos.size();
}
void PhotoManager::markProcessed(const QString &photoPath)
{
m_processedPhotos.append(photoPath);
}
ocrmanager.h
cpp#ifndef OCRMANAGER_H
#define OCRMANAGER_H
#include <QObject>
#include <QString>
#include <QList>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
class OcrManager : public QObject
{
Q_OBJECT
public:
explicit OcrManager(QObject *parent = nullptr);
void setApiKey(const QString &apiKey);
void setSecretKey(const QString &secretKey);
void startProcessing(const QList<QString> &photoPaths);
signals:
void progressUpdated(int percentage);
void logMessage(const QString &msg);
void ocrFinished(const QString &photoPath, const QString &ocrText);
private slots:
void onReplyFinished(QNetworkReply *reply);
private:
QString m_apiKey;
QString m_secretKey;
QNetworkAccessManager *m_networkManager;
QList<QString> m_photoPaths;
int m_processed;
void performOcr(const QString &photoPath);
QByteArray encodePhotoToBase64(const QString &filePath);
};
#endif // OCRMANAGER_H
ocrmanager.cpp
cpp#include "ocrmanager.h"
#include <QFile>
#include <QDir>
#include <QFileInfo>
#include <QTimer>
#include <QJsonParseError>
OcrManager::OcrManager(QObject *parent) : QObject(parent),
m_networkManager(new QNetworkAccessManager(this)),
m_processed(0)
{
// 腾讯云OCR的API端点(以通用文字识别为例)
// 请根据实际API文档调整URL和请求参数
}
void OcrManager::setApiKey(const QString &apiKey)
{
m_apiKey = apiKey;
}
void OcrManager::setSecretKey(const QString &secretKey)
{
m_secretKey = secretKey;
}
void OcrManager::startProcessing(const QList<QString> &photoPaths)
{
m_photoPaths = photoPaths;
m_processed = 0;
// 假设腾讯OCR需要逐个处理照片,这里采用串行处理
// 可以根据需求改为并行处理,但需注意API调用频率限制
QTimer::singleShot(0, this, [this]() {
if (!m_photoPaths.isEmpty()) {
performOcr(m_photoPaths.takeFirst());
}
});
}
void OcrManager::performOcr(const QString &photoPath)
{
QFile file(photoPath);
if (!file.open(QIODevice::ReadOnly)) {
emit logMessage("无法打开文件: " + photoPath);
markProcessed(photoPath);
checkCompletion();
return;
}
QByteArray imageData = file.readAll();
file.close();
// 腾讯OCR通用文字识别API需要将图片进行Base64编码
QByteArray base64Data = imageData.toBase64();
QString base64Str = QString::fromLatin1(base64Data);
// 构建JSON请求体
QJsonObject requestBody;
requestBody["ImageBase64"] = base64Str;
QJsonDocument jsonDoc(requestBody);
QByteArray jsonData = jsonDoc.toJson();
// 设置HTTP请求头
QNetworkRequest request(QUrl("https://ocr.tencentcloudapi.com")); // 替换为实际的API端点
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
// 添加认证头,如API Key等,具体参考腾讯云OCR文档
// request.setRawHeader("Authorization", "Your_Auth_Header");
QNetworkReply *reply = m_networkManager->post(request, jsonData);
connect(reply, &QNetworkReply::finished, this, &OcrManager::onReplyFinished);
}
void OcrManager::onReplyFinished(QNetworkReply *reply)
{
if (reply->error() != QNetworkReply::NoError) {
emit logMessage("OCR请求失败: " + reply->errorString());
} else {
QByteArray responseData = reply->readAll();
QJsonParseError parseError;
QJsonDocument jsonDoc = QJsonDocument::fromJson(responseData, &parseError);
if (parseError.error != QJsonParseError::NoError) {
emit logMessage("JSON解析失败: " + parseError.errorString());
return;
}
if (!jsonDoc.isObject()) {
emit logMessage("无效的JSON响应");
return;
}
QJsonObject jsonObj = jsonDoc.object();
// 根据腾讯OCR的响应结构解析识别结果
// 假设响应中有"TextDetections"数组,每个元素包含"DetectedText"
if (jsonObj.contains("TextDetections") && jsonObj["TextDetections"].isArray()) {
QJsonArray detections = jsonObj["TextDetections"].toArray();
QString ocrText;
for(auto &det : detections) {
if(det.isObject() && det.toObject().contains("DetectedText")) {
ocrText += det.toObject()["DetectedText"].toString() + "
";
}
}
// 去除最后一个换行符
if(!ocrText.isEmpty()) {
ocrText.chop(1);
}
emit ocrFinished(currentPhotoPath(), ocrText.trimmed());
} else {
emit logMessage("未找到识别结果");
}
}
reply->deleteLater();
markProcessed(currentPhotoPath());
checkCompletion();
}
void OcrManager::markProcessed(const QString &photoPath)
{
m_processed++;
// 可以在这里记录已处理的照片路径,防止重复处理
}
void OcrManager::checkCompletion()
{
if(m_processed >= m_photoPaths.size()) { // 注意:这里假设串行处理,m_processed与m_photoPaths.size()对应
emit logMessage("所有照片处理完成!");
// 可以在这里发出完成信号
} else {
// 继续处理下一张照片
QTimer::singleShot(0, this, [this]() {
if(!m_photoPaths.isEmpty()) {
performOcr(m_photoPaths.takeFirst());
}
});
}
}
QString OcrManager::currentPhotoPath() const
{
// 需要在performOcr中记录当前处理的照片路径
// 这里简化处理,假设每次调用performOcr时m_photoPaths的第一个为当前
if(!m_photoPaths.isEmpty()) {
return m_photoPaths.first();
}
return QString();
}
QByteArray OcrManager::encodePhotoToBase64(const QString &filePath)
{
QFile file(filePath);
if (!file.open(QIODevice::ReadOnly)) {
return QByteArray();
}
return file.readAll().toBase64();
}
注意:
https://ocr.tencentcloudapi.com
仅为示例,实际使用时需要替换为腾讯云OCR的具体API地址,并按照腾讯云的文档配置认证信息(如API Key、Secret Key等)。原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。