Qt中各种图像的格式转换以及与cv::Mat图像格式之间的转换

在使用 Qt 与 OpenCV 联合开发时,常常需要在 QImagecv::Mat 之间进行图像格式转换。两者的图像格式不同,因此理解 QImage::Formatcv::Mat 类型的对应关系 非常关键。以下是常见格式的对应关系表:

1. QImage::Formatcv::Mat 类型对照表:

QImage::Format 描述 对应的 cv::Mat 类型 通道顺序
QImage::Format_RGB888 24-bit RGB(每像素3字节) CV_8UC3 RGB → OpenCV中是BGR
QImage::Format_BGR888 24-bit BGR(Qt 5.14+) CV_8UC3 BGR
QImage::Format_ARGB32 32-bit ARGB(Alpha在高字节) CV_8UC4 BGRA(OpenCV里是BGRA)
QImage::Format_ARGB32_Premultiplied 预乘Alpha的32位ARGB CV_8UC4 BGRA(需额外处理Alpha)
QImage::Format_RGB32 32-bit RGB(Alpha总为255) CV_8UC4 BGRA(Alpha恒为255)
QImage::Format_Grayscale8 8-bit 灰度图(Qt 5.5+) CV_8UC1 灰度
QImage::Format_Indexed8 8-bit索引图(常见于灰度图) CV_8UC1 灰度

2. OpenCV图像底层

OpenCV 中的 cv::Mat 图像类型(type)是一个 整数类型常量(int),这个整数中编码了两个关键信息:

  1. 每个通道的数据类型(depth)
  2. 通道数(channels)

本质上的编码方式:

1
type = (depth & 0x7) + ((channels - 1) << 3);

depth 和 channels 的取值

数据类型 depth 宏常量值 二进制值 含义
CV_8U 0 000 8-bit unsigned char
CV_8S 1 001 8-bit signed char
CV_16U 2 010 16-bit unsigned short
CV_16S 3 011 16-bit signed short
CV_32S 4 100 32-bit signed int
CV_32F 5 101 32-bit float
CV_64F 6 110 64-bit double

通道数的编码方式:

通道数编码在高位部分,每个通道是 (channels - 1) << 3

通道数(C) (channels-1)<<3 最终编码中的通道部分
1 0 00000000
2 8 00001000
3 16 00010000
4 24 00011000

举例:CV_8UC3 是怎么计算出来的?

  • CV_8U 的值是 0
  • C3 表示通道数是 3 → (3-1)<<3 = 16
  • 所以 CV_8UC3 = 0 + 16 = 16

cv::Mat::type() 返回的值就是这个整数(16)。

再举几个:

宏定义 分解
CV_8UC1 0 CV_8U + ((1-1)<<3)
CV_8UC2 8 CV_8U + ((2-1)<<3)
CV_8UC3 16 CV_8U + ((3-1)<<3)
CV_8UC4 24 CV_8U + ((4-1)<<3)
CV_16UC1 2 CV_16U + ((1-1)<<3)
CV_16UC3 18 CV_16U + ((3-1)<<3)
CV_32FC1 5 CV_32F + ((1-1)<<3)
CV_32FC3 21 CV_32F + ((3-1)<<3)

2.1 OpenCV 图像类型解释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
====================== OpenCV 图像类型解剖 ======================

类型格式: CV_<bit-depth>{U|S|F}C<channels>
--------------------------------------------------------------
- CV : OpenCV 类型前缀
- bit-depth : 每个通道的位深(8/16/32/64
- U/S/F :
U = unsigned(无符号整数) → uchar/ushort
S = signed(有符号整数) → char/short/int
F = float(浮点数) → float/double
- C<channels>: 通道数(C1~C4)

====================== 常见类型举例 ========================

CV_8UC1 → 8位无符号灰度图 → uchar 灰度图
CV_8UC3 → 8位无符号彩色图(BGR)→ uchar 彩色图(BGR)
CV_8UC4 → 8位无符号4通道(BGRA)→ uchar BGRA 图

CV_16UC1 → 16位无符号灰度图 → ushort 深度图、医学图像
CV_16SC1 → 16位有符号灰度图 → short 常用于梯度图、滤波
CV_32FC1 → 32位浮点灰度图 → float 图像处理、光流
CV_64FC4 → 64位浮点4通道图 → double 高频计算图

==================== 底层编码机制 =======================

OpenCV 图像类型实际是一个整数,编码方式如下:

type = (depth & 0x7) + ((channels - 1) << 3)

depth 类型编码:
CV_8U = 0unsigned char
CV_8S = 1char
CV_16U = 2unsigned short
CV_16S = 3short
CV_32S = 4int
CV_32F = 5float
CV_64F = 6double

通道数编码:(channels - 1) << 3
C1 = 0 << 3 = 0
C2 = 1 << 3 = 8
C3 = 2 << 3 = 16
C4 = 3 << 3 = 24

例:CV_16UC3 = 2(depth) + (3-1)<<3 = 2 + 16 = 18

==========================================================

小技巧:
int depth = CV_MAT_DEPTH(mat.type()); // 数据类型
int channels = CV_MAT_CN(mat.type()); // 通道数

2.2 图像类型解剖图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
        ┌──────────────┐
│ CV_16UC3 │
└─────┬────────┘

┌────────▼────────────┐
│ CV_<Depth><C><N> │
│ │
│ Depth: 16U │ → unsigned short16位无符号)
│ C: C3 │ → 三通道(一般 BGR)
└────────────────────┘

底层编码:
depth = CV_16U = 20000 0010
channels = 3 → (3-1)<<3 = 160001 0000
→ 最终类型 = 2 + 16 = 18

CV_16UC3 = 18

3. 图像格式转换

3.1cv::MatQImage

1
2
3
4
5
6
7
8
9
10
11
12
13
14
QImage Mat2QImage(const cv::Mat& mat)
{
switch (mat.type()) {
case CV_8UC1: // 灰度图
return QImage(mat.data, mat.cols, mat.rows, mat.step, QImage::Format_Grayscale8).copy();
case CV_8UC3: // 彩色图(BGR)
return QImage(mat.data, mat.cols, mat.rows, mat.step, QImage::Format_RGB888).rgbSwapped().copy();
case CV_8UC4: // BGRA 图
return QImage(mat.data, mat.cols, mat.rows, mat.step, QImage::Format_ARGB32).copy();
default:
return QImage(); // 不支持的格式
}
}

3.2 QImagecv::Mat

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
cv::Mat QImage2Mat(const QImage& image)
{
switch (image.format()) {
case QImage::Format_RGB888:
return cv::Mat(image.height(), image.width(), CV_8UC3,
const_cast<uchar*>(image.bits()), image.bytesPerLine()).clone();
case QImage::Format_BGR888:
return cv::Mat(image.height(), image.width(), CV_8UC3,
const_cast<uchar*>(image.bits()), image.bytesPerLine()).clone();
case QImage::Format_ARGB32:
case QImage::Format_RGB32:
return cv::Mat(image.height(), image.width(), CV_8UC4,
const_cast<uchar*>(image.bits()), image.bytesPerLine()).clone();
case QImage::Format_Grayscale8:
case QImage::Format_Indexed8:
return cv::Mat(image.height(), image.width(), CV_8UC1,
const_cast<uchar*>(image.bits()), image.bytesPerLine()).clone();
case QImage::Format_Grayscale16: {
return cv::Mat(image.height(), image.width(), CV_16UC1,
const_cast<uchar*>(image.bits()), image.bytesPerLine()).clone();
}
default:
return cv::Mat();
}
}

4. 注意事项:

  • QImage::Format_RGB888 和 OpenCV 的 CV_8UC3 通道顺序不同(OpenCV默认是 BGR),所以常需要 .rgbSwapped()
  • QImage::Format_RGB32 实际也是 BGRA 格式,只不过 Alpha 通道为 255,OpenCV 也按 4 通道处理。
  • ARGB32_Premultiplied 通常要特别处理 Alpha 通道(不常用)。
  • 为了避免内存共享带来的问题,建议 .copy().clone()