#pragma once
#include <vector>
#include <opencv2/opencv.hpp>
#include <tesseract/baseapi.h>
#include <leptonica/allheaders.h>
#include <thread>
#include <atomic>
#include <mutex>
#include <string>
#include <algorithm>
#include "afxwin.h"
#include <shlwapi.h> // 包含 StrCmpLogicalW 函数
#include <atlimage.h> // 添加CImage头文件
#include <tesseract/baseapi.h>
#include <leptonica/allheaders.h>
#include <iostream>
#include <Windows.h> // 用于获取屏幕分辨率
using namespace cv;
using namespace std;
#pragma comment(lib, "Shlwapi.lib") // 链接 StrCmpLogicalW 所需的库
//#pragma comment(lib, "atlimage.lib") // 链接CImage库
#define WM_UPDATE_DISPLAY_IMAGE (WM_USER + 2)
// B2saomiaotuxiangchuangkou 对话框
class B2saomiaotuxiangchuangkou : public CDialogEx
{
DECLARE_DYNAMIC(B2saomiaotuxiangchuangkou)
public:
B2saomiaotuxiangchuangkou(CWnd* pParent = NULL); // 标准构造函数
virtual ~B2saomiaotuxiangchuangkou();
enum { WM_LOAD_FIRST_IMAGE = WM_USER + 1 };
// 自定义控件布局调整函数(类似 SetControlPosition)
void SetControlPosition(CWnd* pCtrl, int x, int y, int width, int height);
void AdjustLayoutWhenResized();
// 对话框数据
#ifdef AFX_DESIGN_TIME
enum { IDD = IDD_B2saomiaotuxiangchuangkou };
#endif
protected:
virtual void DoDataExchange(CDataExchange* pDX);
virtual BOOL OnInitDialog();
afx_msg void OnSize(UINT nType, int cx, int cy);
afx_msg void OnBnClickedButtonSelectFolder();
afx_msg void OnBnClickedButtonStart();
afx_msg void OnBnClickedButtonPause();
afx_msg void OnBnClickedButtonStop();
afx_msg void OnBnClickedButtonReset();
afx_msg void OnCbnSelchangeComboSaveOption();
afx_msg void OnTimer(UINT_PTR nIDEvent);
afx_msg void OnLvnItemchangedListImages(NMHDR* pNMHDR, LRESULT* pResult);
afx_msg LRESULT OnLoadFirstImageMessage(WPARAM wParam, LPARAM lParam);
afx_msg LRESULT OnUpdateDisplayImage(WPARAM wParam, LPARAM lParam);
afx_msg BOOL OnMouseWheel(UINT nFlags, short zDelta, CPoint pt);
afx_msg BOOL OnEraseBkgnd(CDC* pDC);
DECLARE_MESSAGE_MAP()
private:
void ProcessImages();
void ProcessImage(const CString& filePath);
bool LoadImageToControl(const CString& filePath, UINT controlID);
HBITMAP LoadImageWithOpenCV(const CString& filePath);
HBITMAP LoadImageWithGDIPlus(const CString& filePath);
HBITMAP LoadImageWithCImage(const CString& filePath); // 新增CImage加载函数
HBITMAP MatToBitmap(cv::Mat& mat, UINT controlID); // 新增函数
void UpdateProgressBars();
void Cleanup();
void CreateBackup();
void RestoreBackup();
void SaveImage(cv::Mat& img, const CString& filePath);
/*void SimpleBinarization(cv::Mat& input, cv::Mat& output);*/
void DisplayFirstImage();
CString GetFileNameFromPath(const CString& filePath);
void LoadFilesIntoListControl();
void UpdateDisplayImage(const CString& filePath);
private:
CEdit m_editFolderPath;
CListCtrl m_listImages;
CComboBox m_saveOption;
CButton m_btnStart;
CButton m_btnPause;
CButton m_btnStop;
CButton m_btnReset;
CButton m_btnSelectFolder;
CProgressCtrl m_totalProgress;
CProgressCtrl m_fileProgress;
CStatic m_picOriginal;
CStatic m_picProcessed;
CEdit m_editSavePath;
std::vector<CString> m_imageFiles;
CString m_folderPath;
CString m_savePath;
CString m_backupPath;
std::atomic<bool> m_processing{ false };
std::atomic<bool> m_paused{ false };
std::atomic<bool> m_stopped{ false };
std::atomic<int> m_currentIndex{ 0 };
std::thread m_processingThread;
std::mutex m_mutex;
std::mutex m_imageMutex;
std::mutex m_bitmapMutex;
bool m_firstImageLoaded{ false };
bool m_firstLoadCompleted{ false };
bool m_imageLoaded{ false };
HBITMAP m_currentBitmap{ NULL };
enum ImageLoadMode { MODE_OPENCV, MODE_GDIPlus, MODE_CIMAGE }; // 新增CIMAGE模式
ImageLoadMode m_loadMode{ MODE_CIMAGE }; // 默认使用CImage加载
CRect m_originalRect;
CRect m_processedRect;
private:
// 新增OCR相关函数
cv::Mat rotateImage(const cv::Mat& src, double angle);
cv::Mat resizeToFitScreen(const cv::Mat& img);
void PerformOCRAndRotation(const CString& imagePath, cv::Mat& inputImage, cv::Mat& outputImage);
// 新增Tesseract实例
tesseract::TessBaseAPI m_tess;
bool m_tessInitialized{ false };
};// B2saomiaotuxiangchuangkou.cpp : 实现文件
//
#include "stdafx.h"
#include "Imageprocessing.h"
#include "B2saomiaotuxiangchuangkou.h"
#include "afxdialogex.h"
#include <string>
#include <chrono>
#include <Gdiplus.h>
#include <shlwapi.h>
#pragma comment(lib, "Gdiplus.lib")
#pragma comment(lib, "shlwapi.lib")
#pragma comment(lib, "Shell32.lib")
// GDI+初始化全局变量
Gdiplus::GdiplusStartupInput gdiplusInput;
ULONG_PTR gdiplusToken;
IMPLEMENT_DYNAMIC(B2saomiaotuxiangchuangkou, CDialogEx)
B2saomiaotuxiangchuangkou::B2saomiaotuxiangchuangkou(CWnd* pParent /*=NULL*/)
: CDialogEx(IDD_B2saomiaotuxiangchuangkou, pParent)
{
}
B2saomiaotuxiangchuangkou::~B2saomiaotuxiangchuangkou()
{
Cleanup();
Gdiplus::GdiplusShutdown(gdiplusToken); // 释放GDI+资源
// 释放Tesseract资源
if (m_tessInitialized) {
m_tess.End();
}
}
void B2saomiaotuxiangchuangkou::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
DDX_Control(pDX, IDC_EDIT_FOLDER_PATH, m_editFolderPath);
DDX_Control(pDX, IDC_LIST_IMAGES, m_listImages);
DDX_Control(pDX, IDC_COMBO_SAVE_OPTION, m_saveOption);
DDX_Control(pDX, IDC_BUTTON_START, m_btnStart);
DDX_Control(pDX, IDC_BUTTON_PAUSE, m_btnPause);
DDX_Control(pDX, IDC_BUTTON_STOP, m_btnStop);
DDX_Control(pDX, IDC_BUTTON_RESET, m_btnReset);
DDX_Control(pDX, IDC_BUTTON_SELECT_FOLDER, m_btnSelectFolder);
DDX_Control(pDX, IDC_PROGRESS_TOTAL, m_totalProgress);
DDX_Control(pDX, IDC_PROGRESS_FILE, m_fileProgress);
DDX_Control(pDX, IDC_STATIC_ORIGINAL_IMAGE, m_picOriginal);
DDX_Control(pDX, IDC_STATIC_PROCESSED_IMAGE, m_picProcessed);
DDX_Control(pDX, IDC_EDIT2, m_editSavePath);
}
BEGIN_MESSAGE_MAP(B2saomiaotuxiangchuangkou, CDialogEx)
ON_WM_SIZE()
ON_BN_CLICKED(IDC_BUTTON_SELECT_FOLDER, &B2saomiaotuxiangchuangkou::OnBnClickedButtonSelectFolder)
ON_BN_CLICKED(IDC_BUTTON_START, &B2saomiaotuxiangchuangkou::OnBnClickedButtonStart)
ON_BN_CLICKED(IDC_BUTTON_PAUSE, &B2saomiaotuxiangchuangkou::OnBnClickedButtonPause)
ON_BN_CLICKED(IDC_BUTTON_STOP, &B2saomiaotuxiangchuangkou::OnBnClickedButtonStop)
ON_BN_CLICKED(IDC_BUTTON_RESET, &B2saomiaotuxiangchuangkou::OnBnClickedButtonReset)
ON_CBN_SELCHANGE(IDC_COMBO_SAVE_OPTION, &B2saomiaotuxiangchuangkou::OnCbnSelchangeComboSaveOption)
ON_WM_TIMER()
ON_NOTIFY(LVN_ITEMCHANGED, IDC_LIST_IMAGES, &B2saomiaotuxiangchuangkou::OnLvnItemchangedListImages)
ON_MESSAGE(WM_LOAD_FIRST_IMAGE, &B2saomiaotuxiangchuangkou::OnLoadFirstImageMessage)
ON_MESSAGE(WM_UPDATE_DISPLAY_IMAGE, &B2saomiaotuxiangchuangkou::OnUpdateDisplayImage)
ON_WM_MOUSEWHEEL()
ON_WM_ERASEBKGND()
END_MESSAGE_MAP()
BOOL B2saomiaotuxiangchuangkou::OnInitDialog()
{
CDialogEx::OnInitDialog();
//////////////////////////////////////////////////////////////////////////////////////////////
// 初始化Tesseract OCR
if (m_tess.Init(nullptr, "chi_sim")) {
AfxMessageBox(_T("无法初始化Tesseract OCR引擎!"));
m_tessInitialized = false;
}
else {
m_tessInitialized = true;
}
//////////////////////////////////////////////////////////////////////////////////////////////
m_listImages.SetExtendedStyle(LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES);
m_listImages.InsertColumn(0, _T("文件名"), LVCFMT_LEFT, 200);
m_listImages.InsertColumn(1, _T("大小"), LVCFMT_RIGHT, 100);
m_listImages.InsertColumn(2, _T("类型"), LVCFMT_LEFT, 100);
m_saveOption.AddString(_T("覆盖原图"));
m_saveOption.AddString(_T("新路径"));
m_saveOption.SetCurSel(0);
m_totalProgress.SetRange(0, 100);
m_fileProgress.SetRange(0, 100);
m_totalProgress.SetPos(0);
m_fileProgress.SetPos(0);
m_btnPause.EnableWindow(FALSE);
m_btnStop.EnableWindow(FALSE);
m_btnReset.EnableWindow(FALSE);
m_picOriginal.ModifyStyle(0, SS_BITMAP);
m_picProcessed.ModifyStyle(0, SS_BITMAP);
m_picOriginal.GetWindowRect(&m_originalRect);
ScreenToClient(&m_originalRect);
m_picProcessed.GetWindowRect(&m_processedRect);
ScreenToClient(&m_processedRect);
// 初始化GDI+
Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusInput, NULL);
if (!m_folderPath.IsEmpty()) {
PostMessage(WM_LOAD_FIRST_IMAGE, 0, 0);
}
return TRUE;
}
void B2saomiaotuxiangchuangkou::SetControlPosition(CWnd* pCtrl, int x, int y, int width, int height)
{
if (pCtrl) {
CRect rect(x, y, x + width, y + height);
pCtrl->MoveWindow(rect);
}
}
void B2saomiaotuxiangchuangkou::AdjustLayoutWhenResized()
{
CRect clientRect;
GetClientRect(&clientRect);
int cx = clientRect.Width();
int cy = clientRect.Height();
// 获取屏幕尺寸(可选,若需要基于屏幕比例布局)
int sW = GetSystemMetrics(SM_CXSCREEN);
int sH = GetSystemMetrics(SM_CYSCREEN);
int pc_W1 = (cx - 530) / 3;//图像显示控件宽
int pc_H1;//图像显示控件高
int pc_W2 = (cy - 530) / 3 * 2;//图像显示控件宽
int pc_H2;//图像显示控件高
// 示例:调整子界面中控件的位置(类似你提供的代码)
SetControlPosition(GetDlgItem(IDC_STATIC1), 0, 5, 80, 30); /**/
SetControlPosition(GetDlgItem(IDC_STATIC2), 0, 50, 80, 30); /**/SetControlPosition(GetDlgItem(IDC_EDIT_FOLDER_PATH), 85, 43, 280, 30); /**/SetControlPosition(GetDlgItem(IDC_BUTTON_SELECT_FOLDER), 366, 44, 70, 30); /**/SetControlPosition(GetDlgItem(IDC_BUTTON_START), 440, 43, 70, 30);/**/SetControlPosition(GetDlgItem(IDC_STATIC_ORIGINAL_IMAGE), 530, 5, pc_W1 - 1, 490);/**/SetControlPosition(GetDlgItem(IDC_STATIC_PROCESSED_IMAGE), 530 + pc_W1 + 2, 5, 2 * pc_W1 - 5, cy - 10);
SetControlPosition(GetDlgItem(IDC_STATIC3), 0, 90, 80, 30); /**/SetControlPosition(GetDlgItem(IDC_EDIT2), 85, 83, 280, 30); /**/SetControlPosition(GetDlgItem(IDC_COMBO_SAVE_OPTION), 366, 89, 70, 30); /**/SetControlPosition(GetDlgItem(IDC_BUTTON_PAUSE), 440, 84, 70, 30);
SetControlPosition(GetDlgItem(IDC_STATIC4), 0, 155, 80, 30); /**/ /**/SetControlPosition(GetDlgItem(IDC_BUTTON_STOP), 440, 124, 70, 30);
SetControlPosition(GetDlgItem(IDC_STATIC6), 0, 200, 80, 30); /**/SetControlPosition(GetDlgItem(IDC_PROGRESS_TOTAL), 85, 197, 280, 20); /**/SetControlPosition(GetDlgItem(IDC_BUTTON_RESET), 440, 164, 70, 30);
SetControlPosition(GetDlgItem(IDC_STATIC7), 0, 240, 80, 30); /**/SetControlPosition(GetDlgItem(IDC_PROGRESS_FILE), 85, 237, 180, 20);
SetControlPosition(GetDlgItem(IDC_STATIC8), 0, 305, 80, 30); /**/
/*SetControlPosition(GetDlgItem(IDC_STATIC8), 0, 305, 80, 30); /**/
/*SetControlPosition(GetDlgItem(IDC_STATIC8), 0, 305, 80, 30);*/ /**/
SetControlPosition(GetDlgItem(IDC_LIST_IMAGES), 0, 500, 530 + pc_W1 - 1, cy - 500 - 5); /**/
}
void B2saomiaotuxiangchuangkou::OnSize(UINT nType, int cx, int cy)
{
CDialogEx::OnSize(nType, cx, cy);
if (nType != SIZE_MINIMIZED) {
AdjustLayoutWhenResized();
}
}
void B2saomiaotuxiangchuangkou::LoadFilesIntoListControl()
{
m_listImages.DeleteAllItems();
m_imageFiles.clear();
m_imageLoaded = false;
m_firstLoadCompleted = false;
std::vector<CString> files;
CString searchPath = m_folderPath + _T("\\*.*");
WIN32_FIND_DATA findFileData;
HANDLE hFind = FindFirstFile(searchPath, &findFileData);
if (hFind != INVALID_HANDLE_VALUE) {
do {
if (!(findFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
CString fileName = findFileData.cFileName;
CString ext = fileName.Right(4).MakeLower();
// 扩展支持的图像格式
if (ext == _T(".jpg") || ext == _T(".jpeg") ||
ext == _T(".png") || ext == _T(".bmp") ||
ext == _T(".tif") || ext == _T(".tiff") ||
ext == _T(".webp") || ext == _T(".jp2"))
{
files.push_back(fileName);
}
}
} while (FindNextFile(hFind, &findFileData));
FindClose(hFind);
}
std::sort(files.begin(), files.end(), [](const CString& a, const CString& b) {
return StrCmpLogicalW(a, b) < 0;
});
for (size_t i = 0; i < files.size(); ++i) {
CString fileName = files[i];
CString filePath = m_folderPath + _T("\\") + fileName;
m_imageFiles.push_back(filePath);
int nIndex = m_listImages.InsertItem(i, fileName);
WIN32_FILE_ATTRIBUTE_DATA fad;
ULONGLONG fileSize = 0;
if (GetFileAttributesEx(filePath, GetFileExInfoStandard, &fad)) {
fileSize = ((ULONGLONG)fad.nFileSizeHigh << 32) | fad.nFileSizeLow;
}
CString sizeStr;
if (fileSize < 1024)
sizeStr.Format(_T("%d B"), fileSize);
else if (fileSize < 1024 * 1024)
sizeStr.Format(_T("%.2f KB"), fileSize / 1024.0);
else
sizeStr.Format(_T("%.2f MB"), fileSize / (1024.0 * 1024.0));
m_listImages.SetItemText(nIndex, 1, sizeStr);
CString typeStr = fileName.Right(4).Mid(1).MakeUpper();
m_listImages.SetItemText(nIndex, 2, typeStr);
}
}
void B2saomiaotuxiangchuangkou::OnBnClickedButtonSelectFolder()
{
CFolderPickerDialog dlg;
if (dlg.DoModal() == IDOK)
{
m_folderPath = dlg.GetFolderPath();
m_editFolderPath.SetWindowText(m_folderPath);
LoadFilesIntoListControl();
OnCbnSelchangeComboSaveOption();
m_btnStart.EnableWindow(!m_imageFiles.empty());
if (!m_imageFiles.empty())
{
m_listImages.SetItemState(0, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED);
m_listImages.EnsureVisible(0, FALSE);
m_listImages.UpdateWindow();
PostMessage(WM_LOAD_FIRST_IMAGE, 0, 0);
}
}
}
LRESULT B2saomiaotuxiangchuangkou::OnLoadFirstImageMessage(WPARAM wParam, LPARAM lParam)
{
DisplayFirstImage();
return 0;
}
void B2saomiaotuxiangchuangkou::DisplayFirstImage()
{
if (m_imageFiles.empty()) return;
CString filePath = m_imageFiles[0];
if (!LoadImageToControl(filePath, IDC_STATIC_ORIGINAL_IMAGE)) {
return;
}
m_firstImageLoaded = true;
m_firstLoadCompleted = true;
m_imageLoaded = true;
CWnd* pProcessed = GetDlgItem(IDC_STATIC_PROCESSED_IMAGE);
if (pProcessed) {
auto pStatic = reinterpret_cast<CStatic*>(pProcessed);
HBITMAP hOldBmp = pStatic->GetBitmap();
if (hOldBmp) {
DeleteObject(hOldBmp);
pStatic->SetBitmap(NULL);
}
}
}
bool B2saomiaotuxiangchuangkou::LoadImageToControl(const CString& filePath, UINT controlID)
{
std::lock_guard<std::mutex> lock(m_imageMutex);
DWORD fileAttr = GetFileAttributes(filePath);
if (fileAttr == INVALID_FILE_ATTRIBUTES) {
return false;
}
HBITMAP hBitmap = NULL;
if (m_loadMode == MODE_CIMAGE) {
hBitmap = LoadImageWithCImage(filePath);
if (!hBitmap) {
// CImage加载失败时,切换到OpenCV并重试
m_loadMode = MODE_OPENCV;
hBitmap = LoadImageWithOpenCV(filePath);
if (!hBitmap) {
// OpenCV加载失败时,切换到GDI+
m_loadMode = MODE_GDIPlus;
hBitmap = LoadImageWithGDIPlus(filePath);
}
}
}
else if (m_loadMode == MODE_OPENCV) {
hBitmap = LoadImageWithOpenCV(filePath);
if (!hBitmap) {
m_loadMode = MODE_GDIPlus;
hBitmap = LoadImageWithGDIPlus(filePath);
if (!hBitmap) {
m_loadMode = MODE_CIMAGE;
hBitmap = LoadImageWithCImage(filePath);
}
}
}
else {
hBitmap = LoadImageWithGDIPlus(filePath);
if (!hBitmap) {
m_loadMode = MODE_OPENCV;
hBitmap = LoadImageWithOpenCV(filePath);
if (!hBitmap) {
m_loadMode = MODE_CIMAGE;
hBitmap = LoadImageWithCImage(filePath);
}
}
}
if (!hBitmap) {
return false;
}
CWnd* pWnd = GetDlgItem(controlID);
if (pWnd) {
CStatic* pStatic = dynamic_cast<CStatic*>(pWnd);
if (pStatic) {
HBITMAP oldBmp = pStatic->GetBitmap();
if (oldBmp) DeleteObject(oldBmp);
pStatic->SetBitmap(hBitmap);
pStatic->Invalidate();
return true;
}
}
DeleteObject(hBitmap);
return false;
}
// 使用CImage加载图像并保持比例
HBITMAP B2saomiaotuxiangchuangkou::LoadImageWithCImage(const CString& filePath)
{
CImage image;
if (image.Load(filePath) != S_OK) {
return NULL;
}
CRect rect;
m_picOriginal.GetClientRect(&rect);
int ctrlWidth = rect.Width();
int ctrlHeight = rect.Height();
int srcWidth = image.GetWidth();
int srcHeight = image.GetHeight();
// 计算等比例缩放尺寸,确保不超出控件范围
double ratioX = static_cast<double>(ctrlWidth) / srcWidth;
double ratioY = static_cast<double>(ctrlHeight) / srcHeight;
double ratio = std::min(ratioX, ratioY); // 取较小比例保持原始比例
int drawWidth = static_cast<int>(srcWidth * ratio);
int drawHeight = static_cast<int>(srcHeight * ratio);
int x = (ctrlWidth - drawWidth) / 2;
int y = (ctrlHeight - drawHeight) / 2;
// 创建兼容位图
HDC hdcScreen = ::GetDC(NULL);
HBITMAP hBitmap = CreateCompatibleBitmap(hdcScreen, ctrlWidth, ctrlHeight);
HDC hdcMem = CreateCompatibleDC(hdcScreen);
HBITMAP hOldBmp = (HBITMAP)SelectObject(hdcMem, hBitmap);
// 填充背景为白色
HBRUSH hBrush = CreateSolidBrush(RGB(255, 255, 255));
RECT rc = { 0, 0, ctrlWidth, ctrlHeight };
FillRect(hdcMem, &rc, hBrush);
DeleteObject(hBrush);
// 使用GDI+高质量插值缩放图像
HDC hdcImage = image.GetDC();
// 设置高质量拉伸模式
SetStretchBltMode(hdcMem, HALFTONE);
// 执行高质量缩放
StretchBlt(
hdcMem, x, y, drawWidth, drawHeight,
hdcImage, 0, 0, srcWidth, srcHeight,
SRCCOPY
);
// 释放CImage的设备上下文
image.ReleaseDC();
// 恢复设备上下文并释放资源
SelectObject(hdcMem, hOldBmp);
DeleteDC(hdcMem);
::ReleaseDC(NULL, hdcScreen);
return hBitmap;
}
HBITMAP B2saomiaotuxiangchuangkou::LoadImageWithOpenCV(const CString& filePath)
{
cv::Mat img;
#ifdef _UNICODE
int len = WideCharToMultiByte(CP_UTF8, 0, filePath, -1, NULL, 0, NULL, NULL);
std::vector<char> utf8Buf(len);
WideCharToMultiByte(CP_UTF8, 0, filePath, -1, utf8Buf.data(), len, NULL, NULL);
img = cv::imread(utf8Buf.data(), cv::IMREAD_COLOR);
#else
img = cv::imread(CW2A(filePath), cv::IMREAD_COLOR);
#endif
if (img.empty()) {
return NULL;
}
cv::cvtColor(img, img, cv::COLOR_BGR2RGB);
CRect rect;
m_picOriginal.GetClientRect(&rect);
int dstWidth = rect.Width();
int dstHeight = rect.Height();
double ratio = std::min(static_cast<double>(dstWidth) / img.cols,
static_cast<double>(dstHeight) / img.rows);
int drawWidth = static_cast<int>(img.cols * ratio);
int drawHeight = static_cast<int>(img.rows * ratio);
int x = (dstWidth - drawWidth) / 2;
int y = (dstHeight - drawHeight) / 2;
HDC hdcScreen = ::GetDC(NULL);
HBITMAP hBitmap = CreateCompatibleBitmap(hdcScreen, dstWidth, dstHeight);
HDC hdcMem = CreateCompatibleDC(hdcScreen);
HBITMAP hOldBmp = (HBITMAP)SelectObject(hdcMem, hBitmap);
HBRUSH hBrush = CreateSolidBrush(RGB(255, 255, 255));
RECT rc = { 0, 0, dstWidth, dstHeight };
FillRect(hdcMem, &rc, hBrush);
DeleteObject(hBrush);
cv::Mat resized;
// 根据缩放比例选择插值算法(放大用双三次,缩小用区域插值)
int interpolation = (ratio >= 1.0) ? cv::INTER_CUBIC : cv::INTER_AREA;
cv::resize(img, resized, cv::Size(drawWidth, drawHeight), 0, 0, interpolation);
BITMAPINFOHEADER bmiHeader = { 0 };
bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmiHeader.biWidth = resized.cols;
bmiHeader.biHeight = -resized.rows;
bmiHeader.biPlanes = 1;
bmiHeader.biBitCount = 24;
bmiHeader.biCompression = BI_RGB;
SetStretchBltMode(hdcMem, HALFTONE);
StretchDIBits(
hdcMem,
x, y, drawWidth, drawHeight,
0, 0, resized.cols, resized.rows,
resized.data,
(BITMAPINFO*)&bmiHeader,
DIB_RGB_COLORS,
SRCCOPY
);
SelectObject(hdcMem, hOldBmp);
DeleteDC(hdcMem);
::ReleaseDC(NULL, hdcScreen);
return hBitmap;
}
HBITMAP B2saomiaotuxiangchuangkou::LoadImageWithGDIPlus(const CString& filePath)
{
Gdiplus::Image image(filePath);
if (image.GetLastStatus() != Gdiplus::Ok) {
return NULL;
}
CRect rect;
m_picOriginal.GetClientRect(&rect);
int ctrlWidth = rect.Width();
int ctrlHeight = rect.Height();
int srcWidth = image.GetWidth();
int srcHeight = image.GetHeight();
double scale = std::min(static_cast<double>(ctrlWidth) / srcWidth,
static_cast<double>(ctrlHeight) / srcHeight);
int drawWidth = static_cast<int>(srcWidth * scale);
int drawHeight = static_cast<int>(srcHeight * scale);
int x = (ctrlWidth - drawWidth) / 2;
int y = (ctrlHeight - drawHeight) / 2;
Gdiplus::Bitmap bitmap(ctrlWidth, ctrlHeight, PixelFormat32bppARGB);
Gdiplus::Graphics graphics(&bitmap);
// 设置最高质量渲染参数
graphics.SetSmoothingMode(Gdiplus::SmoothingModeHighQuality);
graphics.SetPixelOffsetMode(Gdiplus::PixelOffsetModeHighQuality);
graphics.SetInterpolationMode(Gdiplus::InterpolationModeHighQualityBicubic);
graphics.Clear(Gdiplus::Color(255, 255, 255));
graphics.DrawImage(&image, x, y, drawWidth, drawHeight);
HBITMAP hBitmap = NULL;
bitmap.GetHBITMAP(Gdiplus::Color::White, &hBitmap);
return hBitmap;
}
// 新增函数:将OpenCV Mat转换为HBITMAP并适配控件
HBITMAP B2saomiaotuxiangchuangkou::MatToBitmap(cv::Mat& mat, UINT controlID)
{
if (mat.empty()) return NULL;
// 确保是3通道RGB图像
if (mat.channels() == 1) {
cv::cvtColor(mat, mat, cv::COLOR_GRAY2BGR);
}
else if (mat.channels() == 4) {
cv::cvtColor(mat, mat, cv::COLOR_BGRA2BGR);
}
CRect rect;
CWnd* pWnd = GetDlgItem(controlID);
if (!pWnd) return NULL;
pWnd->GetClientRect(&rect);
int dstWidth = rect.Width();
int dstHeight = rect.Height();
double ratio = std::min(static_cast<double>(dstWidth) / mat.cols,
static_cast<double>(dstHeight) / mat.rows);
int drawWidth = static_cast<int>(mat.cols * ratio);
int drawHeight = static_cast<int>(mat.rows * ratio);
int x = (dstWidth - drawWidth) / 2;
int y = (dstHeight - drawHeight) / 2;
cv::Mat resized;
// 根据缩放比例选择插值算法
int interpolation = (ratio >= 1.0) ? cv::INTER_CUBIC : cv::INTER_AREA;
cv::resize(mat, resized, cv::Size(drawWidth, drawHeight), ratio, ratio, interpolation);
HDC hdcScreen = ::GetDC(NULL);
HBITMAP hBitmap = CreateCompatibleBitmap(hdcScreen, dstWidth, dstHeight);
HDC hdcMem = CreateCompatibleDC(hdcScreen);
HBITMAP hOldBmp = (HBITMAP)SelectObject(hdcMem, hBitmap);
HBRUSH hBrush = CreateSolidBrush(RGB(255, 255, 255));
RECT rc = { 0, 0, dstWidth, dstHeight };
FillRect(hdcMem, &rc, hBrush);
DeleteObject(hBrush);
BITMAPINFOHEADER bmiHeader = { 0 };
bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmiHeader.biWidth = resized.cols;
bmiHeader.biHeight = -resized.rows;
bmiHeader.biPlanes = 1;
bmiHeader.biBitCount = 24;
bmiHeader.biCompression = BI_RGB;
SetStretchBltMode(hdcMem, HALFTONE);
StretchDIBits(
hdcMem,
x, y, drawWidth, drawHeight,
0, 0, resized.cols, resized.rows,
resized.data,
(BITMAPINFO*)&bmiHeader,
DIB_RGB_COLORS,
SRCCOPY
);
SelectObject(hdcMem, hOldBmp);
DeleteDC(hdcMem);
::ReleaseDC(NULL, hdcScreen);
return hBitmap;
}
BOOL B2saomiaotuxiangchuangkou::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
{
return TRUE;
}
BOOL B2saomiaotuxiangchuangkou::OnEraseBkgnd(CDC* pDC)
{
CRect rect;
GetClientRect(&rect);
CBrush brush(RGB(255, 255, 255));
pDC->FillRect(&rect, &brush);
return TRUE;
}
void B2saomiaotuxiangchuangkou::OnCbnSelchangeComboSaveOption()
{
int option = m_saveOption.GetCurSel();
if (option == 0) {
m_savePath = m_folderPath;
m_editSavePath.SetWindowText(m_savePath);
}
else if (option == 1) {
if (m_savePath.IsEmpty() || m_savePath == m_folderPath) {
CFolderPickerDialog dlg;
if (dlg.DoModal() == IDOK) {
m_savePath = dlg.GetFolderPath();
m_editSavePath.SetWindowText(m_savePath);
}
else {
m_saveOption.SetCurSel(0);
m_savePath = m_folderPath;
m_editSavePath.SetWindowText(m_savePath);
}
}
}
}
void B2saomiaotuxiangchuangkou::OnBnClickedButtonStart()
{
if (m_folderPath.IsEmpty()) {
AfxMessageBox(_T("请先选择文件夹!"));
return;
}
if (m_imageFiles.empty()) {
AfxMessageBox(_T("文件夹中没有图像文件!"));
return;
}
if (m_saveOption.GetCurSel() == 1 && m_savePath.IsEmpty()) {
AfxMessageBox(_T("请先选择保存路径!"));
return;
}
if (m_saveOption.GetCurSel() == 0) {
CreateBackup();
}
m_processing = true;
m_paused = false;
m_stopped = false;
m_currentIndex = 0;
m_btnStart.EnableWindow(FALSE);
m_btnPause.EnableWindow(TRUE);
m_btnStop.EnableWindow(TRUE);
m_btnReset.EnableWindow(FALSE);
m_totalProgress.SetPos(0);
m_fileProgress.SetPos(0);
if (m_processingThread.joinable()) {
m_processingThread.join();
}
m_processingThread = std::thread(&B2saomiaotuxiangchuangkou::ProcessImages, this);
SetTimer(1, 100, NULL);
}
void B2saomiaotuxiangchuangkou::OnBnClickedButtonPause()
{
m_paused = !m_paused;
m_btnPause.SetWindowText(m_paused ? _T("继续") : _T("暂停"));
}
void B2saomiaotuxiangchuangkou::OnBnClickedButtonStop()
{
m_stopped = true;
m_processing = false;
m_btnStart.EnableWindow(TRUE);
m_btnPause.EnableWindow(FALSE);
m_btnStop.EnableWindow(FALSE);
m_btnReset.EnableWindow(TRUE);
if (m_processingThread.joinable()) {
m_processingThread.join();
}
KillTimer(1);
}
void B2saomiaotuxiangchuangkou::OnBnClickedButtonReset()
{
OnBnClickedButtonStop();
if (m_saveOption.GetCurSel() == 0 && !m_backupPath.IsEmpty()) {
RestoreBackup();
}
m_currentIndex = 0;
m_totalProgress.SetPos(0);
m_fileProgress.SetPos(0);
m_backupPath.Empty();
m_btnReset.EnableWindow(FALSE);
if (!m_imageFiles.empty()) {
m_listImages.SetItemState(0, LVIS_SELECTED, LVIS_SELECTED);
PostMessage(WM_LOAD_FIRST_IMAGE, 0, 0);
}
}
void B2saomiaotuxiangchuangkou::OnTimer(UINT_PTR nIDEvent)
{
if (nIDEvent == 1) {
UpdateProgressBars();
}
CDialogEx::OnTimer(nIDEvent);
}
void B2saomiaotuxiangchuangkou::OnLvnItemchangedListImages(NMHDR* pNMHDR, LRESULT* pResult)
{
LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
int nItem = -1;
if (pNMLV) {
if ((pNMLV->uChanged & LVIF_STATE) && (pNMLV->uNewState & LVIS_SELECTED)) {
nItem = pNMLV->iItem;
}
}
else {
nItem = 0;
}
if (!m_firstLoadCompleted && nItem == 0) {
if (pResult) *pResult = 0;
return;
}
// 当用户手动选择时,更新原图并清除处理后的图像
if (nItem >= 0 && nItem < (int)m_imageFiles.size() && !m_processing)
{
CString filePath = m_imageFiles[nItem];
LoadImageToControl(filePath, IDC_STATIC_ORIGINAL_IMAGE);
// 清除处理后的图像
CWnd* pProcessed = GetDlgItem(IDC_STATIC_PROCESSED_IMAGE);
if (pProcessed) {
auto pStatic = reinterpret_cast<CStatic*>(pProcessed);
HBITMAP hOldBmp = pStatic->SetBitmap(NULL);
if (hOldBmp) DeleteObject(hOldBmp);
}
}
if (pResult) {
*pResult = 0;
}
}
void B2saomiaotuxiangchuangkou::ProcessImages()
{
for (m_currentIndex = 0; m_currentIndex < (int)m_imageFiles.size(); ++m_currentIndex)
{
if (m_stopped) break;
while (m_paused && !m_stopped) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
if (m_stopped) break;
ProcessImage(m_imageFiles[m_currentIndex]);
{
std::lock_guard<std::mutex> lock(m_mutex);
int totalProgress = static_cast<int>((static_cast<double>(m_currentIndex + 1) / m_imageFiles.size()) * 100);
m_totalProgress.SetPos(totalProgress);
m_fileProgress.SetPos(100);
}
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
m_processing = false;
m_stopped = true;
PostMessage(WM_COMMAND, MAKEWPARAM(IDC_BUTTON_STOP, BN_CLICKED), (LPARAM)m_btnStop.m_hWnd);
AfxMessageBox(_T("处理完成!"));
}
void B2saomiaotuxiangchuangkou::ProcessImage(const CString& filePath)
{
CT2CA filePathConverted(filePath);
std::string filePathStr(filePathConverted);
cv::Mat image = cv::imread(filePathStr);
if (image.empty()) return;
// 更新显示原图
UpdateDisplayImage(filePath);
cv::Mat processed;
// ====== 调用OCR方向校正函数 ======
// 创建临时副本用于OCR处理
cv::Mat imageCopy = image.clone();
PerformOCRAndRotation(filePath, imageCopy, processed);
// ====== END OCR调用 ======
// 直接显示处理后的图像,不使用临时文件
HBITMAP hProcessedBmp = MatToBitmap(processed, IDC_STATIC_PROCESSED_IMAGE);
if (hProcessedBmp) {
CWnd* pProcessed = GetDlgItem(IDC_STATIC_PROCESSED_IMAGE);
if (pProcessed) {
auto pStatic = reinterpret_cast<CStatic*>(pProcessed);
HBITMAP hOldBmp = pStatic->SetBitmap(NULL);
if (hOldBmp) DeleteObject(hOldBmp);
pStatic->SetBitmap(hProcessedBmp);
pStatic->Invalidate();
}
}
// 保存处理后的图像
if (m_saveOption.GetCurSel() == 0) {
SaveImage(processed, filePath);
}
else {
CString fileName = GetFileNameFromPath(filePath);
CString newPath = m_savePath + _T("\\") + fileName;
SaveImage(processed, newPath);
}
// 更新文件处理进度
for (int progress = 0; progress <= 100; progress += 10) {
if (m_stopped) return;
while (m_paused && !m_stopped) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
if (m_stopped) return;
{
std::lock_guard<std::mutex> lock(m_mutex);
m_fileProgress.SetPos(progress);
}
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
}
void B2saomiaotuxiangchuangkou::UpdateDisplayImage(const CString& filePath)
{
PostMessage(WM_UPDATE_DISPLAY_IMAGE, (WPARAM)(LPCTSTR)filePath, 0);
}
LRESULT B2saomiaotuxiangchuangkou::OnUpdateDisplayImage(WPARAM wParam, LPARAM lParam)
{
CString filePath = (LPCTSTR)wParam;
if (filePath.IsEmpty()) return 0;
// 只在处理过程中更新原图显示
if (m_processing && !m_paused && !m_stopped)
{
LoadImageToControl(filePath, IDC_STATIC_ORIGINAL_IMAGE);
}
return 0;
}
Mat B2saomiaotuxiangchuangkou::rotateImage(const Mat& src, double angle)
{
if (src.empty()) return Mat();
Point2f center(static_cast<float>(src.cols / 2.0),
static_cast<float>(src.rows / 2.0));
Mat rotMat = getRotationMatrix2D(center, angle, 1.0);
Rect2f bbox = RotatedRect(center, Size2f(src.size()), angle).boundingRect2f();
rotMat.at<double>(0, 2) += bbox.width / 2.0 - center.x;
rotMat.at<double>(1, 2) += bbox.height / 2.0 - center.y;
Mat dst;
warpAffine(src, dst, rotMat, Size(static_cast<int>(bbox.width),
static_cast<int>(bbox.height)), INTER_LINEAR, BORDER_CONSTANT, Scalar(0, 0, 0));
return dst;
}
// 调整图像大小以适应屏幕
Mat B2saomiaotuxiangchuangkou::resizeToFitScreen(const Mat& img)
{
if (img.empty()) return Mat();
// 获取屏幕分辨率
int screenWidth = GetSystemMetrics(SM_CXSCREEN);
int screenHeight = GetSystemMetrics(SM_CYSCREEN);
// 设置最大显示尺寸(留出边缘空间)
int maxDisplayWidth = static_cast<int>(screenWidth * 0.8);
int maxDisplayHeight = static_cast<int>(screenHeight * 0.8);
// 计算缩放比例
double scale = min(static_cast<double>(maxDisplayWidth) / img.cols,
static_cast<double>(maxDisplayHeight) / img.rows);
// 如果图像已经小于最大尺寸,则不缩放
if (scale >= 1.0) return img.clone();
// 计算新尺寸
int newWidth = static_cast<int>(img.cols * scale);
int newHeight = static_cast<int>(img.rows * scale);
// 缩放图像
Mat resized;
resize(img, resized, Size(newWidth, newHeight), 0, 0, INTER_AREA);
return resized;
}
void B2saomiaotuxiangchuangkou::PerformOCRAndRotation(const CString& imagePath, cv::Mat& inputImage, cv::Mat& outputImage)
{
// 检查OCR引擎是否初始化
if (!m_tessInitialized) {
outputImage = inputImage.clone(); // 返回原始图像
return;
}
try {
// 使用传入的图像
cv::Mat image = inputImage;
// 设置图像为灰度图 (Tesseract需要)
cv::Mat gray;
cv::cvtColor(image, gray, cv::COLOR_BGR2GRAY);
// 设置Tesseract图像
m_tess.SetImage(gray.data, gray.cols, gray.rows, 1, gray.step);
// 检测方向和脚本
int orient_deg;
float orient_conf;
const char* script_name;
float script_conf;
m_tess.DetectOrientationScript(&orient_deg, &orient_conf, &script_name, &script_conf);
// 根据检测到的方向角度旋转图像(逆时针旋转)
if (orient_deg != 0) {
outputImage = rotateImage(image, orient_deg);
}
else {
outputImage = image.clone();
}
}
catch (...) {
// 处理任何异常,返回原始图像
outputImage = inputImage.clone();
}
// 重置Tesseract图像
m_tess.Clear();
}
void B2saomiaotuxiangchuangkou::SaveImage(cv::Mat& img, const CString& filePath)
{
if (img.empty()) return;
CT2CA filePathConverted(filePath);
std::string filePathStr(filePathConverted);
std::vector<int> compression_params;
CString ext = filePath.Right(4).MakeLower();
if (ext == _T(".jpg") || ext == _T(".jpeg")) {
compression_params.push_back(cv::IMWRITE_JPEG_QUALITY);
compression_params.push_back(95);
}
else if (ext == _T(".png")) {
compression_params.push_back(cv::IMWRITE_PNG_COMPRESSION);
compression_params.push_back(9);
}
cv::imwrite(filePathStr, img, compression_params);
}
void B2saomiaotuxiangchuangkou::UpdateProgressBars()
{
if (m_imageFiles.empty()) return;
int totalProgress = static_cast<int>(
(static_cast<double>(m_currentIndex) / m_imageFiles.size()) * 100);
// 平滑过渡
int currentTotal = m_totalProgress.GetPos();
if (abs(totalProgress - currentTotal) > 5) {
m_totalProgress.SetPos(totalProgress);
}
else if (totalProgress > currentTotal) {
m_totalProgress.SetPos(currentTotal + 1);
}
}
void B2saomiaotuxiangchuangkou::CreateBackup()
{
if (m_folderPath.IsEmpty()) return;
CTime time = CTime::GetCurrentTime();
CString timeStr = time.Format(_T("%Y%m%d%H%M%S"));
m_backupPath = m_folderPath + _T("\\backup_") + timeStr;
if (!CreateDirectory(m_backupPath, NULL)) {
return;
}
for (const auto& file : m_imageFiles) {
CString fileName = GetFileNameFromPath(file);
CString destPath = m_backupPath + _T("\\") + fileName;
CopyFile(file, destPath, FALSE);
}
}
void B2saomiaotuxiangchuangkou::RestoreBackup()
{
if (m_backupPath.IsEmpty()) return;
for (const auto& file : m_imageFiles) {
CString fileName = GetFileNameFromPath(file);
CString sourcePath = m_backupPath + _T("\\") + fileName;
CopyFile(sourcePath, file, FALSE);
}
RemoveDirectory(m_backupPath);
m_backupPath.Empty();
}
void B2saomiaotuxiangchuangkou::Cleanup()
{
m_stopped = true;
m_processing = false;
if (m_processingThread.joinable()) {
m_processingThread.join();
}
KillTimer(1);
std::lock_guard<std::mutex> lock(m_bitmapMutex);
if (m_currentBitmap) {
DeleteObject(m_currentBitmap);
m_currentBitmap = NULL;
}
// 释放控件中的位图
HBITMAP hBmp = m_picOriginal.GetBitmap();
if (hBmp) DeleteObject(hBmp);
hBmp = m_picProcessed.GetBitmap();
if (hBmp) DeleteObject(hBmp);
}
CString B2saomiaotuxiangchuangkou::GetFileNameFromPath(const CString& filePath)
{
int pos = filePath.ReverseFind('\\');
if (pos == -1) pos = filePath.ReverseFind('/');
if (pos != -1) {
return filePath.Mid(pos + 1);
}
return filePath;
}中 ,在void B2saomiaotuxiangchuangkou::PerformOCRAndRotation(const CString& imagePath, cv::Mat& inputImage, cv::Mat& outputImage)
{
// 检查OCR引擎是否初始化
if (!m_tessInitialized) {
outputImage = inputImage.clone(); // 返回原始图像
return;
}
try {
// 使用传入的图像
cv::Mat image = inputImage;
// 设置图像为灰度图 (Tesseract需要)
cv::Mat gray;
cv::cvtColor(image, gray, cv::COLOR_BGR2GRAY);
// 设置Tesseract图像
m_tess.SetImage(gray.data, gray.cols, gray.rows, 1, gray.step);
// 检测方向和脚本
int orient_deg;
float orient_conf;
const char* script_name;
float script_conf;
m_tess.DetectOrientationScript(&orient_deg, &orient_conf, &script_name, &script_conf);
// 根据检测到的方向角度旋转图像(逆时针旋转)
if (orient_deg != 0) {
outputImage = rotateImage(image, orient_deg);
}
else {
outputImage = image.clone();
}
}
catch (...) {
// 处理任何异常,返回原始图像
outputImage = inputImage.clone();
}
// 重置Tesseract图像
m_tess.Clear();
}函数体中输出的outputImage添加一个判断函数,继续对该输出继续处理,判断函数根据#include <opencv2/opencv.hpp>
#include <vector>
#include <iostream>
#include <numeric>
#include <cmath>
#include <future>
#include <algorithm>
using namespace cv;
using namespace std;
// 计算水平投影的方差
double calculateProjectionVariance(const Mat& binary) {
vector<int> projection(binary.rows, 0);
// 使用指针遍历加速
for (int y = 0; y < binary.rows; y++) {
const uchar* row = binary.ptr<uchar>(y);
for (int x = 0; x < binary.cols; x++) {
projection[y] += row[x] / 255; // 归一化到0-1
}
}
// 使用STL算法计算均值和方差
double sum = accumulate(projection.begin(), projection.end(), 0.0);
double mean = sum / projection.size();
double variance = 0.0;
for_each(projection.begin(), projection.end(), [&](int val) {
variance += pow(val - mean, 2);
});
variance /= projection.size();
return variance;
}
// 检测文本倾斜角度 - 优化版本
double detectSkewAngle(const Mat& image) {
// 转为灰度图并二值化
Mat gray, binary;
cvtColor(image, gray, COLOR_BGR2GRAY);
threshold(gray, binary, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);
// 初始角度搜索范围和步长
double angleStart = -15.0;
double angleEnd = 15.0;
double angleStep = 2.0; // 粗粒度搜索
double bestAngle = 0.0;
// 两次迭代搜索:先粗后精
for (int iter = 0; iter < 2; iter++) {
vector<double> angles;
for (double angle = angleStart; angle <= angleEnd; angle += angleStep) {
angles.push_back(angle);
}
double maxVariance = 0.0;
double currentBestAngle = 0.0;
// 多线程并行计算不同角度的投影方差
vector<future<pair<double, double>>> futures;
for (double angle : angles) {
futures.push_back(async(launch::async, [binary, angle]() {
// 获取图像尺寸
int height = binary.rows;
int width = binary.cols;
// 计算旋转矩阵
Point2f center(width / 2.0, height / 2.0);
Mat rot = getRotationMatrix2D(center, angle, 1.0);
// 计算旋转后的图像尺寸
Rect bbox = RotatedRect(center, Size2f(width, height), angle).boundingRect();
// 调整旋转矩阵的平移部分
rot.at<double>(0, 2) += bbox.width / 2.0 - center.x;
rot.at<double>(1, 2) += bbox.height / 2.0 - center.y;
// 旋转图像 (使用更快的插值方法)
Mat rotated;
warpAffine(binary, rotated, rot, bbox.size(), INTER_LINEAR, BORDER_REPLICATE);
// 计算投影方差
double variance = calculateProjectionVariance(rotated);
return make_pair(variance, angle);
}));
}
// 收集结果
for (auto& future : futures) {
auto result = future.get();
if (result.first > maxVariance) {
maxVariance = result.first;
currentBestAngle = result.second;
}
}
// 更新下一次迭代的搜索范围
bestAngle = currentBestAngle;
angleStart = bestAngle - angleStep;
angleEnd = bestAngle + angleStep;
angleStep /= 4.0; // 精搜索步长变为原来的1/4
}
return bestAngle;
}
// 校正倾斜图像 - 优化版本
Mat correctSkew(const Mat& image, double angle) {
// 获取图像尺寸
int height = image.rows;
int width = image.cols;
// 计算旋转矩阵
Point2f center(width / 2.0, height / 2.0);
Mat rot = getRotationMatrix2D(center, angle, 1.0);
// 计算旋转后的图像尺寸
Rect bbox = RotatedRect(center, Size2f(width, height), angle).boundingRect();
// 调整旋转矩阵的平移部分
rot.at<double>(0, 2) += bbox.width / 2.0 - center.x;
rot.at<double>(1, 2) += bbox.height / 2.0 - center.y;
// 使用更快的插值方法
Mat corrected;
warpAffine(image, corrected, rot, bbox.size(), INTER_LINEAR, BORDER_REPLICATE);
return corrected;
}
int main() {
// 读取图像
Mat image = imread("E:\\123\\14.jpg");
if (image.empty()) {
cerr << "无法读取图像!" << endl;
return -1;
}
// 检测倾斜角度
double skewAngle = detectSkewAngle(image);
cout << "检测到倾斜角度: " << skewAngle << "°" << endl;
// 校正倾斜
Mat correctedImage = correctSkew(image, skewAngle);
// 显示结果
namedWindow("原始图像", WINDOW_NORMAL);
namedWindow("校正后图像", WINDOW_NORMAL);
imshow("原始图像", image);
imshow("校正后图像", correctedImage);
// 保存结果
imwrite("corrected_text.jpg", correctedImage);
cout << "校正后的图像已保存为 corrected_text.jpg" << endl;
waitKey(0);
return 0;
}和#include <opencv2/opencv.hpp>
#include <iostream>
#include <vector>
#include <cmath>
#include <algorithm>
#include <limits>
using namespace cv;
using namespace std;
// 计算两条直线的夹角差(0-90度)
double calculateAngleDiff(double angle1, double angle2) {
double diff = fabs(angle1 - angle2);
if (diff > 90) diff = 180 - diff;
return diff;
}
// 计算直线角度(0-180度)
double getLineAngle(const Vec4i& line) {
Point pt1(line[0], line[1]);
Point pt2(line[2], line[3]);
double dx = pt2.x - pt1.x;
double dy = pt2.y - pt1.y;
double angle = atan2(dy, dx) * 180 / CV_PI;
if (angle < 0) angle += 180;
return angle;
}
// 调整图像大小以适应屏幕显示
Mat resizeToFitScreen(const Mat& image, int maxWidth = 1200, int maxHeight = 800) {
double scale = 1.0;
if (image.cols > maxWidth || image.rows > maxHeight) {
double scaleX = static_cast<double>(maxWidth) / image.cols;
double scaleY = static_cast<double>(maxHeight) / image.rows;
scale = min(scaleX, scaleY);
}
if (scale < 1.0) {
Mat resized;
resize(image, resized, Size(), scale, scale);
return resized;
}
return image.clone();
}
// 检测四边形轮廓
vector<vector<Point>> detectQuadrilaterals(Mat& edges) {
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
findContours(edges, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE);
vector<vector<Point>> quads;
for (size_t i = 0; i < contours.size(); i++) {
// 忽略小轮廓
if (contourArea(contours[i]) < 1000) continue;
vector<Point> approx;
// 多边形逼近
double epsilon = 0.02 * arcLength(contours[i], true);
approxPolyDP(contours[i], approx, epsilon, true);
// 如果是四边形
if (approx.size() == 4) {
// 检查是否是凸四边形
if (isContourConvex(approx)) {
quads.push_back(approx);
}
}
}
return quads;
}
// 计算四边形的最小外接矩形角度
double getQuadrilateralAngle(const vector<Point>& quad) {
RotatedRect rect = minAreaRect(quad);
return rect.angle;
}
// 检查两个四边形是否嵌套(一个在另一个内部)
bool isNestedQuad(const vector<Point>& outer, const vector<Point>& inner) {
for (const Point& pt : inner) {
if (pointPolygonTest(outer, pt, false) < 0) {
return false;
}
}
return true;
}
int main() {
// 硬编码图片路径 - 修改为您需要的实际路径
string imagePath = "C:/path/to/your/image.jpg"; // Windows路径
// string imagePath = "/home/user/images/test.jpg"; // Linux路径
// 1. 读取图片
Mat src = imread(imagePath);
if (src.empty()) {
cerr << "Error: Could not open or find the image at: " << imagePath << endl;
cerr << "Please check the path and try again." << endl;
return -1;
}
// 2. 转换为灰度图并进行边缘检测
Mat gray, edges;
cvtColor(src, gray, COLOR_BGR2GRAY);
GaussianBlur(gray, gray, Size(5, 5), 0);
Canny(gray, edges, 50, 150);
// 3. 霍夫变换检测直线
vector<Vec4i> lines;
HoughLinesP(edges, lines, 1, CV_PI / 180, 50, 50, 10);
// 4. 过滤短直线
double diag = sqrt(src.rows * src.rows + src.cols * src.cols);
double minLength = diag / 20;
vector<Vec4i> validLines;
vector<double> angles;
for (const auto& line : lines) {
Point pt1(line[0], line[1]);
Point pt2(line[2], line[3]);
double length = norm(pt1 - pt2);
if (length > minLength) {
validLines.push_back(line);
angles.push_back(getLineAngle(line));
}
}
// 5. 检测四边形轮廓
vector<vector<Point>> quads = detectQuadrilaterals(edges);
bool hasNestedQuads = false;
double nestedQuadAngle = 0;
// 寻找嵌套的四边形(外框和内框)
for (size_t i = 0; i < quads.size(); i++) {
for (size_t j = 0; j < quads.size(); j++) {
if (i == j) continue;
// 检查是否嵌套
if (isNestedQuad(quads[i], quads[j])) {
hasNestedQuads = true;
nestedQuadAngle = getQuadrilateralAngle(quads[j]); // 使用内框的角度
break;
}
}
if (hasNestedQuads) break;
}
// 6. 处理检测结果
Mat dst = src.clone();
string resultText;
double rotationAngle = 0;
bool foundFrame = false;
const double angleTolerance = 5.0;
// 如果有嵌套四边形(粗边框)
if (hasNestedQuads) {
foundFrame = true;
rotationAngle = nestedQuadAngle;
resultText = "Nested frame detected. Rotation angle: " + to_string(rotationAngle) + " degrees";
}
// 否则检测水平和垂直线
else if (validLines.size() >= 2) {
// 分组存储水平和垂直方向的直线
vector<Vec4i> horizontalLines, verticalLines;
for (size_t i = 0; i < validLines.size(); i++) {
if (angles[i] < angleTolerance || angles[i] > 180 - angleTolerance ||
(angles[i] > 90 - angleTolerance && angles[i] < 90 + angleTolerance)) {
if (fabs(angles[i] - 90) < angleTolerance) {
verticalLines.push_back(validLines[i]);
} else {
horizontalLines.push_back(validLines[i]);
}
}
}
// 检查是否构成线框
if (horizontalLines.size() >= 2 && verticalLines.size() >= 2) {
foundFrame = true;
// 计算水平线平均角度
double avgAngle = 0;
int count = 0;
for (double angle : angles) {
if (angle < angleTolerance || angle > 180 - angleTolerance) {
avgAngle += (angle > 90) ? angle - 180 : angle;
count++;
}
}
if (count > 0) {
avgAngle /= count;
rotationAngle = avgAngle;
}
resultText = "Frame detected. Rotation angle: " + to_string(rotationAngle) + " degrees";
} else {
// 检查是否存在平行/共线直线
bool foundParallel = false;
for (size_t i = 0; i < angles.size(); i++) {
for (size_t j = i + 1; j < angles.size(); j++) {
double angleDiff = calculateAngleDiff(angles[i], angles[j]);
if (angleDiff < angleTolerance) {
foundParallel = true;
break;
}
}
if (foundParallel) break;
}
if (foundParallel) {
resultText = "Parallel lines detected but no frame found";
} else {
resultText = "No frame or parallel lines detected";
}
}
} else {
resultText = "No sufficient lines detected";
}
cout << resultText << endl;
// 7. 如果有线框,进行校正
if (foundFrame) {
// 旋转图像校正
Point2f center(src.cols / 2.0f, src.rows / 2.0f);
Mat rotMat = getRotationMatrix2D(center, rotationAngle, 1.0);
warpAffine(src, dst, rotMat, src.size(), INTER_LINEAR, BORDER_CONSTANT, Scalar(255, 255, 255));
}
// 8. 保存结果
size_t dotPos = imagePath.find_last_of(".");
string baseName = (dotPos == string::npos) ? imagePath : imagePath.substr(0, dotPos);
string ext = (dotPos == string::npos) ? ".jpg" : imagePath.substr(dotPos);
string origPath = baseName + "_original" + ext;
string correctedPath = baseName + "_corrected" + ext;
imwrite(origPath, src);
if (foundFrame) {
imwrite(correctedPath, dst);
cout << "Saved original image: " << origPath << endl;
cout << "Saved corrected image: " << correctedPath << endl;
} else {
cout << "Saved original image: " << origPath << endl;
}
// 9. 显示结果(调整大小以适应屏幕)
Mat displaySrc = resizeToFitScreen(src);
Mat displayDst = resizeToFitScreen(dst);
// 计算字体大小比例
double fontScaleSrc = min(0.7, 600.0 / displaySrc.cols);
double fontScaleDst = min(0.7, 600.0 / displayDst.cols);
// 添加文本到显示图像
putText(displaySrc, resultText, Point(20, 40), FONT_HERSHEY_SIMPLEX,
fontScaleSrc, Scalar(0, 0, 255), 2);
if (foundFrame) {
putText(displayDst, "Corrected Image", Point(20, 40), FONT_HERSHEY_SIMPLEX,
fontScaleDst, Scalar(0, 0, 255), 2);
}
// 显示原始图像
namedWindow("Original Image", WINDOW_NORMAL);
imshow("Original Image", displaySrc);
// 显示校正图像(如果有)
if (foundFrame) {
namedWindow("Corrected Image", WINDOW_NORMAL);
imshow("Corrected Image", displayDst);
}
waitKey(0);
return 0;
}这两个函数添加,判断条件符合哪一个函数就执行哪一个函数的功能(前提是将后两个函数的功能合理的嵌合到第一个代码中)
最新发布