Windows Practice_内存映射_加载BMP

什么是BMP,为什么我们会经常使用这种图片格式?

微软给我们专门提供了一个加载位图的LoadBitmap函数,这说明bmp格式图片在Windows中使用的比较多。
我们知道没一种文件都有它自己的文件结构,那么bmp就是一种位图形式的文件格式。既然bmp也是一种文件,那么我们也可以使用CreateFile函数进行打开。

bmp文件格式详解

先看下面一个简单的例子:

void CFileMapBMPDlg::OnBnClickedButtonLoadBmp()
{
    Clean();

    CFileDialog file_dialog(TRUE);
    if (file_dialog.DoModal() == IDOK)
    {
        m_hFile = CreateFile(file_dialog.GetPathName(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
        if (m_hFile != INVALID_HANDLE_VALUE)
        {
            m_hMap = CreateFileMapping(m_hFile, nullptr, PAGE_READONLY, 0, 0, nullptr);
            if (m_hMap != nullptr)
            {
                m_pBaseAddr = MapViewOfFile(m_hMap, FILE_MAP_READ, 0, 0, 0);

                Display();
                m_bLoad = TRUE;
            }
        }
    }
}

void CFileMapBMPDlg::Display()
{
    BITMAPFILEHEADER *pBitMapHead = (BITMAPFILEHEADER *)m_pBaseAddr;
    if (pBitMapHead->bfType == MAKEWORD('B', 'M'))
    {
        BYTE *pBitMapData = (BYTE *)m_pBaseAddr + pBitMapHead->bfOffBits;
        BITMAPINFO* pBitMapInfoHead = (BITMAPINFO*)(((BYTE *)m_pBaseAddr) + sizeof(BITMAPFILEHEADER));

        int nWidth = pBitMapInfoHead->bmiHeader.biWidth;
        int nHeight = pBitMapInfoHead->bmiHeader.biHeight;

        CClientDC dc(this);
        HDC hMemDC = CreateCompatibleDC(dc);
        HBITMAP hBitmap = CreateCompatibleBitmap(dc, nWidth, nHeight);
        SelectObject(hMemDC, hBitmap);
        SetDIBitsToDevice(hMemDC, 0, 0, nWidth, nHeight, 0, 0, 0, nHeight, pBitMapData, pBitMapInfoHead, DIB_RGB_COLORS);
        BitBlt(dc, 0, 0, nWidth, nHeight, hMemDC, 0, 0, SRCCOPY);

        DeleteObject(hBitmap);
        DeleteObject(hMemDC);
    }
}

void CFileMapBMPDlg::Clean() const
{
    if (m_hFile != INVALID_HANDLE_VALUE)
    {
        CloseHandle(m_hFile);
    }
    if (m_hMap != nullptr)
    {
        CloseHandle(m_hMap);
    }
    if (m_pBaseAddr != nullptr)
    {
        UnmapViewOfFile(m_pBaseAddr);
    }
}

bmp图片格式目前使用的比较广泛,比如大漠插件的图像识别功能就是使用的bmp格式图片。
位图使用的一个例子:如果加载位图的时候,只想加载我们自己位图图片,那么可以在位图的某些特定位置加上特定的像素值,在加载的时候按断这些位置的像素值是否是预设定的值,如果是就可以加载,如果不是就不需要加载了。

我们在操作bmp文件的时候,有三个结构体很关键,BITMAPFILEHEADER、BITMAPINFO和BITMAPINFOHEADER, 正是由于这三个结构体的巧妙设置,才使得经历了这么多年的bmp文件在升级的时候成本降到最小。

typedef struct tagBITMAPFILEHEADER {
  WORD  bfType;
  DWORD bfSize;
  WORD  bfReserved1;
  WORD  bfReserved2;
  DWORD bfOffBits;
} BITMAPFILEHEADER, *PBITMAPFILEHEADER;
typedef struct tagBITMAPINFO {
  BITMAPINFOHEADER bmiHeader;
  RGBQUAD          bmiColors[1];
} BITMAPINFO, *PBITMAPINFO;
typedef struct tagBITMAPINFOHEADER {
  DWORD biSize;
  LONG  biWidth;
  LONG  biHeight;
  WORD  biPlanes;
  WORD  biBitCount;
  DWORD biCompression;
  DWORD biSizeImage;
  LONG  biXPelsPerMeter;
  LONG  biYPelsPerMeter;
  DWORD biClrUsed;
  DWORD biClrImportant;
} BITMAPINFOHEADER, *PBITMAPINFOHEADER;

我们发现,BITMAPFILEHEADER结构体做的事情很少,所以它是不需要升级的;但是它可以通过bfOffBits成员找到BITMAPINFO结构体,然后再通过bmiHeader结构体成员的

这是因为BITMAPINFOHEADER结构体中的biSize成员biSize进行结构体取值,如此一来,整个bmp文件的升级成本就会降到最低。通过结构体中的一个size成员变量,就能很好的将软件的升级成本降到最低,这也说明了为什么微软很喜欢在结构体中使用一个标志结构体大小的size变量了。这也能够给我们在开发自己的程序的时候,如何将软件升级的成本将为最低。这个思想值得借鉴!!!

得到位图信息后,我们该如何将这个信息显示到界面上呢?需要使用SetDIBitsToDevice函数进行显示,SetDIBitsToDevice函数在设备上指定的矩形中设置像素,与目标设备上下文关联,使用DIB、JPEG或PNG图像的颜色数据。它的函数原型如下:

int SetDIBitsToDevice(
  _In_       HDC        hdc,
  _In_       int        XDest,
  _In_       int        YDest,
  _In_       DWORD      dwWidth,
  _In_       DWORD      dwHeight,
  _In_       int        XSrc,
  _In_       int        YSrc,
  _In_       UINT       uStartScan,
  _In_       UINT       cScanLines,
  _In_ const VOID       *lpvBits,
  _In_ const BITMAPINFO *lpbmi,
  _In_       UINT       fuColorUse
);
总结

使用内存映射加载BMP位图比从文件加载(也就是直接使用LoadBitmap函数)加载位图的速度要快很多。这种方式在游戏中用的很广泛。

这种方式必须一次全部加载到内存中,否则就会映射失败。

内存映射的另一个应用-共享空间

它可以共享数据,
我们知道每个进程与进程进制在逻辑上是分离的,但是在表现形式上,这两者又是相关的。所以在两个进程中数据共享是比较麻烦的。我们可以使用文件来共享数据,但是毫无疑问,通过文件来交换数据的速度是非常慢的。所以通过内存映射来共享数据的速度就变得非常的快了。
内存映射是通过内核对象来管理的。使用CreateFile创建内存映射的时候,给最后一个参数指定一个名字,就可以使用这个名字用OpenProcess函数来进行数据共享,但是这样需要注意两点:1.原来的内存映射的HANDLE不能关闭;2.访问冲突的问题,所以需要做同步,使用event就可以了。
我们如果要做进程间通信应该使用管道或者socket。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,首先需要在代码中引入SE注意力模块所需的库和函数: ```python import tensorflow as tf def se_block(input_feature, ratio=8): """Squeeze-and-excitation block""" channel_axis = 1 if tf.keras.backend.image_data_format() == "channels_first" else -1 channel = input_feature.shape[channel_axis] se_feature = tf.keras.layers.GlobalAveragePooling2D()(input_feature) se_feature = tf.keras.layers.Reshape((1, 1, channel))(se_feature) se_feature = tf.keras.layers.Dense(channel // ratio, activation='relu', kernel_initializer='he_normal', use_bias=True)(se_feature) se_feature = tf.keras.layers.Dense(channel, activation='sigmoid', kernel_initializer='he_normal', use_bias=True)(se_feature) if tf.keras.backend.image_data_format() == 'channels_first': se_feature = tf.keras.layers.Permute((3, 1, 2))(se_feature) se_tensor = tf.keras.layers.multiply([input_feature, se_feature]) return se_tensor ``` 然后在代码中的残差块中添加SE注意力模块: ```python def residual_block(inputs, filters, strides=(1, 1), use_se=True): shortcut = inputs # first block x = tf.keras.layers.Conv2D(filters=filters, kernel_size=(3, 3), strides=strides, padding='same', kernel_initializer='he_normal')(inputs) x = tf.keras.layers.BatchNormalization()(x) x = tf.keras.layers.Activation('relu')(x) # second block x = tf.keras.layers.Conv2D(filters=filters, kernel_size=(3, 3), strides=(1, 1), padding='same', kernel_initializer='he_normal')(x) x = tf.keras.layers.BatchNormalization()(x) # SE block if use_se: x = se_block(x) # shortcut connection if strides != (1, 1) or inputs.shape[-1] != filters: shortcut = tf.keras.layers.Conv2D(filters=filters, kernel_size=(1, 1), strides=strides, padding='same', kernel_initializer='he_normal')(inputs) shortcut = tf.keras.layers.BatchNormalization()(shortcut) x = tf.keras.layers.Add()([x, shortcut]) x = tf.keras.layers.Activation('relu')(x) return x ``` 这样就在代码中添加了SE注意力模块。注意,这里的实现方式是在残差块中嵌入SE注意力模块,而不是在整个模型中添加。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值