以下是一个使用 MFC 编写的 VC++2012 图像去底灰的示例,该示例加载一张位图、去掉背景并显示处理后的位图。
首先,在 MFC 应用程序中添加一个对话框,并在其上添加一个静态文本控件和一个按钮。将按钮命名为 “Open”,并为它添加响应函数 OnBnClickedOpen。
打开 Resource.h 文件,并添加以下几行代码:
#define IDC_STATIC_IMAGE 1000
这将定义一个名为 IDC_STATIC_IMAGE 的静态文本控件 ID。
然后,打开 DialogNameDlg.h 文件,并添加以下成员变量和函数声明:
private:
CBitmap m_bitmap;
BYTE* m_lpBitsSrc = nullptr;
BYTE* m_lpBitsDst = nullptr;
BITMAPINFOHEADER m_biSrc = {0};
BITMAPINFOHEADER m_biDst = {0};
void LoadImage(LPCTSTR lpszPath);
BOOL LoadBitmapData();
void RemoveBackground();
void ShowImage();
这些成员变量包括源图像和目标图像的像素数据指针、位图信息头等。同时,还声明了几个用于加载图像、去除背景、显示图像等操作的函数。
接下来,在 DialogNameDlg.cpp 文件中实现这些函数,具体代码如下:
void CDialogNameDlg::OnBnClickedOpen()
{
// 加载图像
LPCTSTR lpszFilter = _T(“JPEG Files (.jpg)|.jpg|Bitmap Files (.bmp)|.bmp|All Files (.)|.||”);
CFileDialog dlg(TRUE, NULL, NULL, OFN_FILEMUSTEXIST | OFN_HIDEREADONLY, lpszFilter, this);
if (dlg.DoModal() == IDOK)
{
LoadImage(dlg.GetPathName());
RemoveBackground();
ShowImage();
}
}
void CDialogNameDlg::LoadImage(LPCTSTR lpszPath)
{
m_bitmap.Attach((HBITMAP)LoadImage(NULL, lpszPath, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE));
ASSERT(m_bitmap.m_hObject != NULL);
// 获取位图信息头和像素数据
CDC dc;
dc.CreateCompatibleDC(NULL);
dc.SelectObject(&m_bitmap);
m_biSrc.biSize = sizeof(BITMAPINFOHEADER);
m_biSrc.biBitCount = 32;
GetDIBits(dc.m_hDC, m_bitmap, 0, 0, NULL, (LPBITMAPINFO)&m_biSrc, DIB_RGB_COLORS);
m_lpBitsSrc = new BYTE[m_biSrc.biSizeImage];
GetDIBits(dc.m_hDC, m_bitmap, 0, m_biSrc.biHeight, m_lpBitsSrc, (LPBITMAPINFO)&m_biSrc, DIB_RGB_COLORS);
}
BOOL CDialogNameDlg::LoadBitmapData()
{
// 创建目标图像并分配内存
m_biDst.biSize = sizeof(BITMAPINFOHEADER);
m_biDst.biWidth = m_biSrc.biWidth;
m_biDst.biHeight = m_biSrc.biHeight;
m_biDst.biPlanes = 1;
m_biDst.biBitCount = 24;
int iNumBytes = ((m_biDst.biWidth * m_biDst.biBitCount + 31) & (~31)) / 8;
m_biDst.biSizeImage = iNumBytes * m_biDst.biHeight;
if (m_lpBitsDst != nullptr)
{
delete[] m_lpBitsDst;
m_lpBitsDst = nullptr;
}
m_lpBitsDst = new BYTE[m_biDst.biSizeImage];
if ((m_lpBitsSrc == NULL) || (m_lpBitsDst == NULL))
return FALSE;
memcpy(&m_biDst, &m_biSrc, sizeof(BITMAPINFOHEADER));
m_biDst.biBitCount = 24;
m_biDst.biCompression = BI_RGB;
// 分配内存块
HGLOBAL hMemDst = GlobalAlloc(GMEM_MOVEABLE, m_biDst.biSizeImage);
BYTE* lpDst = (BYTE*)GlobalLock(hMemDst);
memcpy(lpDst, m_lpBitsDst, m_biDst.biSizeImage);
GlobalUnlock(hMemDst);
return TRUE;
}
void CDialogNameDlg::RemoveBackground()
{
if (!LoadBitmapData())
{
AfxMessageBox(_T(“Error: Failed to allocate memory!”));
return;
}
// 去掉底色灰度值最大的部分
int iWidthBytes = ((m_biDst.biWidth * m_biDst.biBitCount + 31) & (~31)) / 8;
for (int y = 0; y < m_biDst.biHeight; y++)
{
for (int x = 0; x < m_biDst.biWidth; x++)
{
BYTE* lpPixel = m_lpBitsDst + y * iWidthBytes + x * 3;
BYTE b = *(lpPixel);
BYTE g = *(lpPixel + 1);
BYTE r = *(lpPixel + 2);
double gray = 0.2126 * r + 0.7152 * g + 0.0722 * b;
if (gray > 240)
{
*(lpPixel) = 255;
*(lpPixel + 1) = 255;
*(lpPixel + 2) = 255;
}
}
}
}
void CDialogNameDlg::ShowImage()
{
// 显示图像
CRect rc;
GetDlgItem(IDC_STATIC_IMAGE)->GetClientRect(&rc);
CDC* pDC = GetDlgItem(IDC_STATIC_IMAGE)->GetDC();
CDC memDC;
CBitmap bmpMem;
memDC.CreateCompatibleDC(pDC);
bmpMem.CreateCompatibleBitmap(pDC, rc.Width(), rc.Height());
memDC.SelectObject(&bmpMem);
StretchDIBits(memDC.m_hDC,
0, 0, rc.Width(), rc.Height(),
0, 0, m_biDst.biWidth, m_biDst.biHeight,
m_lpBitsDst, (LPBITMAPINFO)&m_biDst, DIB_RGB_COLORS, SRCCOPY);
pDC->BitBlt(0, 0, rc.Width(), rc.Height(), &memDC, 0, 0, SRCCOPY);
ReleaseDC(pDC);
在 LoadImage 函数中,我们使用 LoadImage 和 GetDIBits 函数加载图像并读取其像素数据。在 LoadBitmapData 函数中,我们创建目标位图并为其分配内存。然后,在 RemoveBackground 函数中,我们遍历每个像素,并计算其灰度值。如果灰度值大于 240(即背景),则将该像素设为白色。最后,在 ShowImage 函数中,我们将修改后的像素数据绘制到静态文本控件上。
这是一个简单的示例,可以根据需要进行优化和扩展。
需要在 DialogNameDlg.h 文件中添加以下 include 和宏定义:
需要在 DialogNameDlg.h 文件中添加以下 include 和宏定义:
cpp
Copy code
#pragma once
#include
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0501
#endif
#ifndef WINVER
#define WINVER 0x0501
#endif
这些 include 主要是为了使用一些 Windows API,并且在编译时使用正确的版本号。
接下来,在 DialogNameDlg.cpp 文件中实现 LoadImage 函数和 RemoveBackground 函数,具体代码如下:
cpp
Copy code
void CDialogNameDlg::LoadImage(LPCTSTR lpszPath)
{
HBITMAP hBitmap = (HBITMAP)LoadImage(nullptr, lpszPath, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE | LR_CREATEDIBSECTION);
if (hBitmap != nullptr)
{
BITMAP bm;
GetObject(hBitmap, sizeof(bm), &bm);
m_biSrc.biSize = sizeof(BITMAPINFOHEADER);
m_biSrc.biWidth = bm.bmWidth;
m_biSrc.biHeight = bm.bmHeight;
m_biSrc.biPlanes = 1;
m_biSrc.biBitCount = 24;
int iNumBytes = ((m_biSrc.biWidth * m_biSrc.biBitCount + 31) & (~31)) / 8;
m_biSrc.biSizeImage = iNumBytes * m_biSrc.biHeight;
CDC* pDC = GetDC();
CDC memDC;
memDC.CreateCompatibleDC(pDC);
void* lpvBits = nullptr;
HBITMAP hDib = CreateDIBSection(memDC.m_hDC, (BITMAPINFO*)&m_biSrc, DIB_RGB_COLORS, &lpvBits, nullptr, 0);
if (hDib != nullptr)
{
memDC.SelectObject(hDib);
memDC.DrawState(CPoint(0, 0), CSize(m_biSrc.biWidth, m_biSrc.biHeight), hBitmap, DSS_NORMAL, (CBrush*)nullptr);
GetDIBits(memDC.m_hDC, hDib, 0, m_biSrc.biHeight, lpvBits, (BITMAPINFO*)&m_biSrc, DIB_RGB_COLORS);
m_lpBitsSrc = (BYTE*)lpvBits;
DeleteObject(hDib);
}
ReleaseDC(pDC);
DeleteObject(hBitmap);
}
}
void CDialogNameDlg::RemoveBackground()
{
if (m_lpBitsSrc == nullptr)
return;
// 创建目标位图
m_biDst.biSize = sizeof(BITMAPINFOHEADER);
m_biDst.biWidth = m_biSrc.biWidth;
m_biDst.biHeight = m_biSrc.biHeight;
m_biDst.biPlanes = 1;
m_biDst.biBitCount = 24;
int iNumBytes = ((m_biDst.biWidth * m_biDst.biBitCount + 31) & (~31)) / 8;
m_biDst.biSizeImage = iNumBytes * m_biDst.biHeight;
std::vector<BYTE> data(m_biDst.biSizeImage, 0);
m_lpBitsDst = data.data();
if (m_lpBitsDst == nullptr)
return;
// 去掉底色灰度值最大的部分
for (int y = 0; y < m_biDst.biHeight; y++)
{
for (int x = 0; x < m_biDst.biWidth; x++)
{
BYTE* lpPixelSrc = m_lpBitsSrc + y * (m_biDst.biWidth * 3) + x * 3;
BYTE* lpPixelDst = m_lpBitsDst + y * iNumBytes + x * 3;
BYTE b = *(lpPixelSrc);
BYTE g = *(lpPixelSrc + 1);
BYTE r = *(lpPixelSrc + 2);
double gray = 0.2126 * r + 0.7152 * g + 0.0722 * b;
if (gray > 240)
{
*(lpPixelDst) = 255;
*(lpPixelDst + 1) = 255;
*(lpPixelDst + 2) = 255;
}
else
{
*(lpPixelDst) = b;
*(lpPixelDst + 1) = g;
*(lpPixelDst + 2) = r;
}
}
}
}
在 LoadImage 函数中,我们使用 LoadImage 函数加载图像,并使用 CreateDIBSection 函数创建一个与位图兼容的 DIB。然后,我们使用 DrawState 函数将位图绘制到 DIB 上,并使用 GetDIBits 函数读取像素数据。
在 RemoveBackground 函数中,与之前一样,我们遍历每个像素,并计算其灰度值。如果灰度值大于 240(即背景),则将该像素设为白色;否则,将该像素保留,并将它复制到目标位图中。
最后,在 ShowImage 函数中,我们需要稍作修改,以便使用 CreateCompatibleBitmap 函数创建一个与静态文本控件兼容的位图,并将修改后的像素数据绘制到其中:
cpp
Copy code
void CDialogNameDlg::ShowImage()
{
// 显示图像
CRect rc;
GetDlgItem(IDC_STATIC_IMAGE)->GetClientRect(&rc);
CDC* pDC = GetDlgItem(IDC_STATIC_IMAGE)->GetDC();
CDC memDC;
CBitmap bmpMem;
memDC.CreateCompatibleDC(pDC);
bmpMem.CreateCompatibleBitmap(pDC, m_biDst.biWidth, m_biDst.biHeight);
memDC.SelectObject(&bmpMem);
SetStretchBltMode(memDC.m_hDC, COLORONCOLOR);
StretchDIBits(memDC.m_hDC,
0, 0, m_biDst.biWidth, m_biDst.biHeight,
0, 0, m_biDst.biWidth, m_biDst.biHeight,
m_lpBitsDst, (LPBITMAPINFO)&m_biDst, DIB_RGB_COLORS, SRCCOPY);
pDC->BitBlt(0, 0, rc.Width(), rc.Height(), &memDC, 0, 0, SRCCOPY);
ReleaseDC(pDC);
}
现在,我们已经成功地实现了图像去底灰的功能。但是,请注意,这只是一种简单的方法,效果可能不如其他更高级的算法。