Windows设备场景函数 - GetDC Ex

本文详细介绍了VB中GetDCEx函数的使用方法及其与GetDC函数的区别。GetDCEx函数可以为指定窗口获取设备场景,并提供更多的选项来定制设备场景的特性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

VB声明
Declare Function GetDCEx Lib "user32" Alias "GetDCEx" (ByVal hwnd As Long, ByVal hrgnclip As Long, ByVal fdwOptions As Long) As Long
说明
为指定窗口获取设备场景。相比GetDC,本函数提供了更多的选项
返回值
Long,执行成功为指定窗口设备场景句柄。出错则为0
参数表
参数类型及说明
hwndLong,窗口句柄
hrgnclipLong,窗口剪裁区
fdwOptionsLong,标志字。根据下列常数设置各位:
DCX_CACHE不管窗口类的样式,从windows缓存获取设备场景
DCX_CLIPCHILREN所有可见的子窗口区都要从DC的剪裁区中排除
DCX_CLIPSIBLINGS窗口hWnd上的所有可见兄弟窗口都要从DC的剪裁区中排除
DCX_EXCLUDERGN从DC剪裁区中排除由hrgnclip指定的区域
DCX_EXCLUDEUPDATE从设备场景剪裁区中排除刷新区域
DCX_INTERSECTRGN由hrgnclip指定的区域与设备场景剪裁区相交
DCX_INTERSECTUPDATE指定区域与设备场景刷新区域相交
DCX_LOCKWINDOWUPDATE该标志为允许向窗口绘图,即使它由于LockWindowUpdate的调用被锁住
DCX_NORESETATTRS设备场景释放后不被重置为默认状态
DCX_PARENTCLIP放弃CS_PARENTDC类样式设置。DC的起点设为hWnd窗口的左上角
DCX_WINDOWA device context is returned for the entire window rectangle rather than just the client area of the window
DCX_VALIDATECombine with DCX_INTERSECTUPDATE, validates the clipping region
注解

若窗口所属类具有CS_OWNDC, CS_CLASSDC 或 CS_PARENTDC样式,则获取的设备场景属窗口或类专有。这时,设备场景状态不能从初值修改。vb的窗体和控件通常是这种情况。否则,置DCX_CACHE位以从通用windows缓冲区恢复设备场景。若不置该位,则函数返回0。DC的状态位默认设置。从缓存获取的设备场景用过后要用ReleaseDC函数释放以防止系统死锁,因为windows只有5个缓存DC可用
其他情况参见GetDC函数注解

#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; }这两个函数添加,判断条件符合哪一个函数就执行哪一个函数的功能(前提是将后两个函数的功能合理的嵌合到第一个代码中)
最新发布
07-05
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值