OpenCV 图像像素类型转换与归一化

一、知识点
1、OpenCV支持多种数据类型,每种类型都对应着不同的取值范围。

(1)、CV_8U取值范围[0, 255]。

(2)、CV_16U取值范围[0, 65535]。

(3)、CV_32F取值范围[0, 1]。

2、OpenCV提供convertTo()函数来转换数据类型,提供normalize()函数来改变取值范围。

3、void Mat::convertTo(OutputArray dst, int rtype, double alpha, double beta) const

(1)、Mat成员函数,将Mat对象转换到另一种数据类型。

(2)、参数说明:

dst: 输出数组。

rtype: 输出数组的期望类型,如CV_8UC1、CV_8UC3、CV_32FC3等。

alpha: 缩放因子,默认值为1,用于乘以输入矩阵的每个元素。

beta: 偏移量,默认值为0,用于将缩放后的值加上偏移量。

4、void normalize( InputArray src,
InputOutputArray dst,
double alpha = 1,
double beta = 0,
int norm_type = NORM_L2,
int dtype = -1,
InputArray mask = noArray());

(1)、将数组归一化到指定的取值范围。

(2)、参数说明:

src: 输入数组。

dst: 输出数组,其类型和输入数组相同。

alpha: 归一化后的最小值或缩放系数,具体取决于norm_type。 若为NORM_MINMAX则表示归一化后的最小值; 若为NORM_INF、NORM_L1、NORM_L2则表示缩放系数。

beta: 归一化后的最大值,仅当norm_type为NORM_MINMAX时有效。

norm_type: 归一化的方式,cv::NormTypes枚举值,详见5。

dtype: 为负数时,dst的类型和src相同; 否则,dst的通道数和src相同,但深度为dtype。

mask: 可选掩码,若不为空,则在指定数组中(非零掩码对应)归一化; 若为空,则在整个数组归一化。

(3)、注意,norm_type为NORM_MINMAX时,虽然alpha表示最小值,beta表示最大值,但是两值调换也不影响。

5、enum NormTypes {
NORM_INF = 1,
NORM_L1 = 2,
NORM_L2 = 4,
NORM_L2SQR = 5,
NORM_HAMMING = 6,
NORM_HAMMING2 = 7,
NORM_TYPE_MASK = 7,
NORM_RELATIVE = 8,
NORM_MINMAX = 32
};

(1)、常用NORM_INF、NORM_L1、NORM_L2、NORM_MINMAX这四种归一化方式。

(2)、NORM_L1,所有像素所有通道的值和为1。
cv::normalize(src, dst, 1.0, 0.0, cv::NORM_L1);
sum = 2 + 8 + 10 = 20
2 ---> 0.1 (2.0 / 20.0)
8 ---> 0.4 (8.0 / 20.0)
10 ---> 0.5 (10.0 / 20.0)

(3)、NORM_L2,所有像素所有通道的值求单位向量。 (默认)
cv::normalize(src, dst, 1.0, 0.0, cv::NORM_L2);
|v| = 开根号(2 * 2 + 8 * 8 + 10 * 10) = 开根号(168) = 12.96
2 ---> 0.15 (2.0 / 12.96)
8 ---> 0.62 (8.0 / 12.96)
10 ---> 0.77 (10.0 / 12.96)

(4)、NORM_INF,每个值除以所有像素所有通道的最大值。
cv::normalize(src, dst, 1.0, 0.0, cv::NORM_INF);
Max = 10
2 ---> 0.2 (2.0 / 10.0)
8 ---> 0.8 (8.0 / 10.0)
10 ---> 1.0 (10.0 / 10.0)

(5)、NORM_MINMAX (常用)
cv::normalize(src, dst, 0.0, 1.0, cv::NORM_MINMAX);
alpha = 0.0, beta = 1.0, 归一化范围[0.0, 1.0],简记为[a, b]
找到样本数据最小值Min = 2, 最大值Max = 10
计算系数k = (b - a) / (Max - Min) = 1.0 / 8.0 = 0.125
归一化值 = a + k * (当前值 - Min)
2 ---> 0.0 (0.0 + 0.125 * (2 - 2))
8 ---> 0.75 (0.0 + 0.125 * (8 - 2))
10 ---> 1.0 (0.0 + 0.125 * (10 - 2))

6、注意:

(1)、将CV_8UC3转换为CV_32FC3,8U取值范围[0, 255],32F取值范围[0.0, 1.0],如果不归一化,imshow会显示几乎全白图像。

(2)、将CV_8UC3图像不转换类型,直接归一化,imshow会显示全黑图片。

二、示例代码

cpp 复制代码
#include <iostream>
#include <opencv2/opencv.hpp>


int main()
{
    //自定义src1,CV_8UC3类型的src1转为CV_32FC3类型的dst1,类型值改变但是元素值不变
    cv::Mat src1 = cv::Mat::zeros(3, 3, CV_8UC3);
    src1 = cv::Scalar(45, 60, 80);
    std::cout << "src1 type = " << src1.type() << std::endl << src1 << std::endl;
    cv::Mat dst1;
    src1.convertTo(dst1, CV_32FC3, 1.0, 0.0);
    std::cout << "dst1 type = " << dst1.type() << std::endl << dst1 << std::endl;

    //NORM_L1, 所有像素所有通道的值求和为1
    cv::Mat dst2;
    cv::normalize(dst1, dst2, 1.0, 0.0, cv::NORM_L1);
    std::cout << "dst2 type = " << dst2.type() << std::endl << dst2 << std::endl;

    //NORM_L2, 所有像素所有通道的值求单位向量
    cv::Mat dst3;
    cv::normalize(dst1, dst3, 1.0, 0.0, cv::NORM_L2);
    std::cout << "dst3 type = " << dst3.type() << std::endl << dst3 << std::endl;

    //NORM_INF, 每个值除以所有像素所有通道的最大值
    cv::Mat dst4;
    cv::normalize(dst1, dst4, 1.0, 0.0, cv::NORM_INF);
    std::cout << "dst4 type = " << dst4.type() << std::endl << dst4 << std::endl;

    //NORM_MINMAX
    cv::Mat dst5;
    cv::normalize(dst1, dst5, 0.0, 1.0, cv::NORM_MINMAX);
    std::cout << "dst5 type = " << dst5.type() << std::endl << dst5 << std::endl;

    //src2是读取的图像
    cv::Mat src2 = cv::imread("../images/6.png");
    if (src2.empty())
    {
        std::cout << "load src2 image error..." << std::endl;
        return -1;
    }
    cv::imshow("原始图像", src2);

    //CV_8UC3转为32FC3,值没有变化,但是显示会非常白的图片
    src2.convertTo(src2, CV_32FC3);
    cv::imshow("类型转换", src2);

    //NORM_L1,结果值太小,图像显示黑色
    cv::Mat dst6;
    cv::normalize(src2, dst6, 1.0, 0.0, cv::NORM_L1);
    cv::imshow("NORM_L1", dst6);

    //NORM_L2,结果值太小,放大100倍,图像能显示出来
    cv::Mat dst7;
    cv::normalize(src2, dst7, 100.0, 0.0, cv::NORM_L2);
    cv::imshow("NORM_L2", dst7);

    //NORM_INF
    cv::Mat dst8;
    cv::normalize(src2, dst8, 1.0, 0.0, cv::NORM_INF);
    cv::imshow("NORM_INF", dst8);

    //NORM_MINMAX
    cv::Mat dst9;
    cv::normalize(src2, dst9, 1.0, 0.0, cv::NORM_MINMAX);
    cv::imshow("NORM_MINMAX", dst9);

    //src3和src2读取一样的原始图片, 但如果不转换类型,直接归一化,会显示全黑的图片
    cv::Mat src3 = cv::imread("../images/6.png");
    if (src3.empty())
    {
        std::cout << "load src3 image error..." << std::endl;
        return -1;
    }
    cv::Mat dst10;
    cv::normalize(src3, dst10, 1.0, 0.0, cv::NORM_MINMAX);
    cv::imshow("直接归一化", dst10);

    cv::waitKey(0);
    return 0;
}

输出结果:

cpp 复制代码
src1 type = 16
[ 45,  60,  80,  45,  60,  80,  45,  60,  80;
  45,  60,  80,  45,  60,  80,  45,  60,  80;
  45,  60,  80,  45,  60,  80,  45,  60,  80]
dst1 type = 21
[45, 60, 80, 45, 60, 80, 45, 60, 80;
 45, 60, 80, 45, 60, 80, 45, 60, 80;
 45, 60, 80, 45, 60, 80, 45, 60, 80]
dst2 type = 21
[0.027027028, 0.036036037, 0.048048049, 0.027027028, 0.036036037, 0.048048049, 0.027027028, 0.036036037, 0.048048049;
 0.027027028, 0.036036037, 0.048048049, 0.027027028, 0.036036037, 0.048048049, 0.027027028, 0.036036037, 0.048048049;
 0.027027028, 0.036036037, 0.048048049, 0.027027028, 0.036036037, 0.048048049, 0.027027028, 0.036036037, 0.048048049]
dst3 type = 21
[0.13678823, 0.1823843, 0.24317907, 0.13678823, 0.1823843, 0.24317907, 0.13678823, 0.1823843, 0.24317907;
 0.13678823, 0.1823843, 0.24317907, 0.13678823, 0.1823843, 0.24317907, 0.13678823, 0.1823843, 0.24317907;
 0.13678823, 0.1823843, 0.24317907, 0.13678823, 0.1823843, 0.24317907, 0.13678823, 0.1823843, 0.24317907]
dst4 type = 21
[0.5625, 0.75, 1, 0.5625, 0.75, 1, 0.5625, 0.75, 1;
 0.5625, 0.75, 1, 0.5625, 0.75, 1, 0.5625, 0.75, 1;
 0.5625, 0.75, 1, 0.5625, 0.75, 1, 0.5625, 0.75, 1]
dst5 type = 21
[2.4214387e-08, 0.42857146, 1, 2.4214387e-08, 0.42857146, 1, 2.4214387e-08, 0.42857146, 1;
 2.4214387e-08, 0.42857146, 1, 2.4214387e-08, 0.42857146, 1, 2.4214387e-08, 0.42857146, 1;
 2.4214387e-08, 0.42857146, 1, 2.4214387e-08, 0.42857146, 1, 2.4214387e-08, 0.42857146, 1]