添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

使用C++ opencv将16位图像转换为8位

现在有一张16bit深度的图像,如果不使用PS或者其他工具的话,是很难直接获取到图像里储存的信息的。如下。

直接在Window里打开一张16位tif格式的图片
直接在Window里打开一张16位tif格式的图片

如果能将16位转换成8位的话,就能正常显示了。

一张16位的图像,意思是一张图像的每个像素点的像素值都由16位的二进制数表示,每个像素点的颜色有 2^16 = 65536 种可能。

也就是说,图像的颜色区间被划分成了 2^16 = 65536 份。

同理,8位图像,图像的颜色区间被划分成了 2^8 = 256 份。

那么,将16位转换成8位,不就是将区间 [0,65535] 映射到 [0,255] 吗?

  • VS2017
  • OPENCV 3.41

测试与分析

16位到8位,是高精度到低精度的转换,必然造成 信息的丢失
在实际的测试中也是这样。

下面是笔者直接将16位映射到8位的转换结果

这实际上是一张 失真很严重 的图像。

上面这种方法虽然道理是没错,但是这种转换遗失了图像中太多信息,不是我们想要的。然而当我在百度上搜索关于16位到8位图像的转换思路的时候,提供的却大多是这种方法,这也是我写这篇博客的原因。

问题的原因是我

错误地认为了16位的图像像素值就是分布在 [0,65535] 的。

解决的方法是:

取图像 像素值 最小值 到 最大值 区间映射到[0,255]。

最终处理结果如下:
在这里插入图片描述

是不是感觉细节层次丰富了很多呢?

#include <iostream>
#include <opencv.hpp>
#include <opencv\highgui.h>
#include <stdlib.h>
#include <time.h>
#include <windows.h>
using namespace cv;
using namespace std;
int main()
	DWORD Start_time = GetTickCount(); //计时开始
	Mat img = imread("task1.tif", CV_LOAD_IMAGE_UNCHANGED);//加载图像;
	int width = img.cols;//图片宽度
	int height = img.rows;//图片高度
	Mat dst = Mat::zeros(height, width, CV_8UC1);//先生成空的目标图片
	double minv = 0.0, maxv = 0.0;
	double* minp = &minv;
	double* maxp = &maxv;
	minMaxIdx(img, minp, maxp);  //取得像素值最大值和最小值
	//用指针访问像素,速度更快
	ushort *p_img;
	uchar *p_dst;
	for (int i = 0; i < height; i++)
		p_img = img.ptr<ushort>(i);//获取每行首地址
		p_dst = dst.ptr<uchar>(i);
		for (int j = 0; j < width; ++j)
			p_dst[j] = (p_img[j] - minv) / (maxv - minv) * 255;
			//下面是失真较大的转换方法
			//int temp = img.at<ushort>(i, j);
			//dst.at<uchar>(i, j) = temp;
	DWORD End_time = GetTickCount(); //计时结束
	cout << "Time used:" << End_time - Start_time << " ms"<<'\n';
	imshow("8bit image", dst);
	imwrite("task1_8bit.jpg", dst);
	waitKey(0);
	system("pause");
	return 0;

不光是16位,24位图像的转换也是同样的思路,感兴趣的朋友可以自己试一试。
不过要另外多说一句,高精度到低精度,信息的丢失是不可避免的,上面提供的方法只是相对效果更好。

补充: 关于数据类型的说明
ushort 为无符号16位整数,占2个字节,取值范围在0~65,535之间。
uchar 为无符号8位,占1个字节,取值范围在0~255之间。

要转换数据记得选择合适的数据类型哦。

Mat src16,tmp; Mat dst8 = Mat::zeros(src16.size(), CV_8U); normalize(src16, tmp, 0, 255, NORM_MINMAX); convertScaleAbs(tmp, dst8); 二、自己代码实现 Mat src16; Mat dst8 = Mat::zeros(src16.size(), CV_8U); double mymin, mymax; cv::minMaxIdx(s opencv常用的样色空间包括RGB, HSV和YUV等。RGB颜色空间是基于三基色原理二形成的,常用于图像显示系统中;HSV描述的色度,饱和度,亮度这些表示颜色得方法,常用于描述色彩变化;YUV是通过亮度和色度来描述颜色,色度由UV通道组合而成。 opencv提供cvtColor(inputArray src, outputArray dst, int code, int dstCn = 0) src是输入图像原,可以是8位CV_8U或者16位CV_16U无符号整形,或者单精度浮点数CV_32F;code是颜色空间转换模式,常用的有有CV_RGB2GRAY, CV_RGB2HSV,CV_ 本小工具实现了,交互的改善图像色调。其概念是实验性的调整图像的亮度和对比度,以便在合适的灰度范围提供最多的细节。 彩色本身并不改变。在RGB和CMYK空间中,这意味着使用相同的变换函数映射3个(或4个)彩色分量。在HSI中则改进了亮度分量; 下面显示了3个常见的色调不平衡的几个典型变换----平淡的,较亮的,较暗的图像。 S型曲线可以增强对比度,凹凸曲线分别减小、增加亮度。 如之前所说,我们需要将16位图像转换为8位图像;16位图像的像素值一共有:2^16=65536种颜色;而8位位图像只有:2^8=256种颜色,传统的位数转换都是:像素值*256/65536,比如photoshop,以及matlab的im2uint8函数都是如此,在一般场景下是没有问题的,我们姑且称之为“真转换”,而如果是labelme得到的label.png标注图像在进行转换时,由于每个类别的像素............... //#if                   编译预处理中的条件命令,相当于C语法中的if语句 //#ifdef              判断某个宏是否被定义,若已定义,执行随后的语句 //#ifndef            与#ifdef相反,判断某个宏是否未被定义 //#elif  ... Scalar 它将各个通道的值构成一个整体,赋给具有相同通道数的矩阵元素,通俗点就是一个复合数据。destroyWindow(const char* windowName) :销毁指定窗口。32位真彩色,即在24位真彩色图像的基础上再增加一个表示图像透明度信息的Alpha通道。16位增强色,16位彩色,每个像素所能显示的彩色数为2的16次方,即65536种颜色。24位真彩色,每个像素所能显示的彩色数为24位,即2的24次方,约1680万种颜色。8位色,每个像素所能显示的彩色数为2的8次方,即256种颜色。 %DVBS2Constellation 信号星座取自 ETSI EN 302 307 % [星座,位图] = DVBS2星座(MODSCHEME,GAMMA) % 返回 DVB-S2 中指定的星座点和位映射% 标准 ETSI EN 302 307。输出向量 CONSTELLATION 包含% 星座点和输出向量 BITMAPPING 包含相关的% 位映射。 这些向量的数据被组织成使得向量% 可以直接输入 sigmapper 或 llr_demod_mex 构造函数。 % % DVB-S2 标准 ETSI EN 302 307 规定了信号星座%四种不同的调制方案:QPSK,8PSK,16APSK和32APSK。 % % MODSCHEME 表示调制方案。 GAMMA 设置星座半径使用星座方案 16APSK 和2APSK。 如果使用 QPSK 和 8PSK 调制方案,则无需额外% 需要输 opencv中Mat存在各种类型,其中mat有一个type()的函数可以返回该Mat的类型。类型表示了矩阵中元素的类型以及矩阵的通道个数,它是一系列的预定义的常量,其命名规则为CV_(位数)+(数据类型)+(通道数)。具体的有以下值: 1.Unsigned 8bits(一般的图像文件格式使用的大小)IplImage数据结构参数:IPL_DEPTH_8UCvMat数据结构参数:CV_8UC1,CV_...