HTML5/JavaScript 图像自动Gamma校正 — 打造图像处理类库第一步

今天为大家带来的是一篇关于图像自动Gamma校正的文章,主要是利用了html5的canvas做图像处理。一般处理Gamma校正都是通过用户手动调节Gamma值来完成的,自动Gamma校正则是通过一系列的计算得出理想的Gamma值后再做处理。网上关于JavaScript做Gamma校正的文章很少,而自动Gamma校正的文章可以说是几乎找不到,这篇文章里图像自动Gamma校正的相关内容,是我通过查阅有限的资料实现的,所以还是挺有成就感的。今天分享给大家,如果文中有不正确的地方,欢迎大家指出来;有更好的想法也欢迎大家提出来,我们一起探讨学习。感兴趣的话可到文末的链接下载源码,程序的效果如下:


忽略gif图片画质过差的问题,你会发现自动Gamma校正后,图片的整体亮度提高了,石头的细节部分也能看到了。因为这里选用的图片原本是比较暗的,所以最后图片变亮了,如果选用的图片过亮,经过自动Gamma校正后,图片则会变暗。


1. 关于Gamma(伽马)
1.1 什么是Gamma

Gamma(伽马)是图像处理和视频制作中常用的技术用语。它定义了一个像素的数值和它的实际亮度之间的关系。从数学的角度来说,Gamma是用来改变某些输入值的“幂指数”。可以用如下简短的公式进行说明。


下面分别以2,1和0.5的Gamma值为例来简单说明这个公式


根据函数图,Gamma与输入、输出值间的关系就一目了然了,简单总结如下:
①当Gamma值比1大时,在输入值相同的情况下,输出值减小;
②当Gamma值为1时,输出值不变;
③当Gamma值比1小时,在输入值相同的情况下,输出值增加。

1.2 什么是Gamma校正
我是以前使用Photoshop时,才第一次接触到伽马校正(Gamma Correction),但当时并不知道Gamma校正的含义,现在通过一系列调查,才对它有了一个清晰的认识。简而言之,Gamma校正是对动态范围内亮度的非线性存储/还原算法,即输入值进行的非线性操作,使输出值与输入值呈指数关系;从效果上来说Gamma校正调整图像的整体亮度,没有校正的图像看起来可能会存在过亮或太暗的情况,所以想要图像显示效果更完美,Gamma校正就显得很重要了。

使用Photoshop做Gamma校正时,如果设置的Gamma比1大,输出值会增加,即图片会变亮;而如果设置的Gamma值比1小时,输出值会减小,即图片会变暗。似乎不符合我们上一节针对公式所做的总结,刚好是相反的,这是因为PhotoShop是以Gamma的倒数作为输入值的幂,来计算输出值,如下所示:


我们后面的实现也是基于这个公式。

1.3 为什么需要Gamma校正
主要的原因有两个方面:

① 人类对于外界刺激变化程度的感受,不是线性的,是指数形式的,如下图所示:


也就是亮度相同的几盏灯,人类对于1盏灯→2盏灯产生的亮度变化感受,与2盏灯→4盏灯的变化感受是相同的。

而且人类对于较暗的细节更加敏感,光越亮我们对于亮度的变化就不是那么敏感了。所以通过Gamma校正,可以尽可能的保留暗的部分的细节,以满足人眼对暗部敏感的需求。

② 图片文件的色阶很有限,24位色图片每个通道只有2^8个色阶,总共只能显示2^24种颜色;而且亮度级别过于庞大,如果按照线性方案进行存储,那么存储/传输的代价就会变得很昂贵。


2. 基本运用

调整亮度,以及灰度图像中的Gamma校正,使用上一节介绍的指数函数把每个像素的RGB值进行变换。具体执行下列转换公式(假定像素值的取值范围为0到255):


使用之前提到的输入值的倒数幂,来完成Gamma校正。


3. 如何实现Gamma校正
利用 canvas = document.createElement('canvas'); 创建出 canvas 元素;
再通过 canvas.getContext('2d')方法得到 CanvasRenderingContext2D 对象,这个对象实现了画布绘制所使用的大多数方法;
然后调用 CanvasRenderingContext2D 对象的 drawImage() 方法将图片绘制到 canvas 上;
之后使用 CanvasRenderingContext2D 对象的 getImageData() 方法得到 ImageData 对象,该对象拷贝了画布指定矩形的像素数据,即该对象中的每个像素,都保存着 RGBA 值,也就是红、绿、蓝以及 alpha 通道,这些数据 (color/alpha信息)以数组形式存在,并存储于 ImageData对象的 data 属性中;
对每个像素的RGB值进行Gamma校正,调用的是我们自己实现的 resetPixelColor() 方法;
最后,调用 CanvasRenderingContext2D 对象的 putImageData()方法将图像数据拷贝回画布上,这时 canvas 上的数据就是经过Gamma校正后的了,如果要得到图片数据,再通过调用 canvas.toDataURL('image/png') 方法就可以得到Base64的图片数据。

下面把Gamma校正的代码贴出来:

function adjustImageGamma(img) {
	image = img;
	canvas = document.createElement('canvas');
	canvas.id = img.id;
	canvas.className = img.className;
	//canvas.width = img.naturalWidth;
	canvas.width = img.width;
	//canvas.height = img.naturalHeight;
	canvas.height = img.height;
	
	ctx = canvas.getContext('2d');
	//ctx.drawImage(img, 0, 0);
	ctx.drawImage(img, 0, 0, img.width, img.height);
	
	var image_parentNode = img.parentNode;
	image_parentNode.replaceChild(canvas, img);
		
	//imageData = ctx.getImageData(0, 0, img.naturalWidth, img.naturalHeight);
	imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
	
	var gammaVal = getGammaVal();
	gammaCorrection = 1 / gammaVal;
	
	setTimeout(function() {
		//for ( y = 0; y < image.naturalHeight; y++) {
		for ( y = 0; y < canvas.height; y++) {
			//for ( x = 0; x < image.naturalWidth; x++) {
			for ( x = 0; x < canvas.width; x++) {
				var index = parseInt(x + canvas.width * y) * 4;
				resetPixelColor(index);
			}
		}
		ctx.putImageData(imageData, 0, 0);
		//var dataURL = canvas.toDataURL('image/png');
	}, 0);
}

function resetPixelColor(index) {
	imageData.data[index + 0] = Math.pow(
		(imageData.data[index + 0] / 255), gammaCorrection) * 255;
	imageData.data[index + 1] = Math.pow(
		(imageData.data[index + 1] / 255), gammaCorrection) * 255;
	imageData.data[index + 2] = Math.pow(
		(imageData.data[index + 2] / 255), gammaCorrection) * 255;
}

4. 如何实现自动Gamma校正

上一节的方法中调用过 getGammaVal() 方法来计算出gamma值,这节就具体介绍下这个值的计算过程:
将图片的像素数据转变为非RGB的数据来计算均值,我们这里是将其转变为灰度数据。之所以要转变成非RGB的的色彩模式,是因为如果使用RGB的数据来计算均值,可能会造成色彩平衡偏移。
将计算出来的均值带入 gammaVal = log(mean/255)/log(midrange) 这个公式中,就可以得到Gamma值了。
总的来说,计算Gamma值的过程并不复杂,但之前在调查这部分内容花费了我挺多时间的,因为网上的资料实在是太少了。好了,接下来就把代码贴出来:

function getGammaVal() {
	var pixData = imageData.data;
	var grayNum = pixData.length / 4;
	var totalGrayVal = 0;
	for (var i = 0; i < pixData.length; i += 4) {
		/* RGB to Luma: 
		 * http://stackoverflow.com/questions/37159358/save-canvas-in-grayscale */
		//var grayscale = pix[i] * 0.2126 + pix[i+1] * 0.7152 + pix[i+2] * 0.0722;
		var grayscale = (pixData[i] + pixData[i+1] + pixData[i+2]) / 3;
		totalGrayVal = totalGrayVal + grayscale;
	}
	var mean = totalGrayVal / grayNum;
	var gammaVal = Math.log10(mean/255) / Math.log10(0.5);
	return gammaVal;
}

需要注意的问题:

之前用火狐浏览器运行程序没报错,但是在谷歌的Chrome浏览器运行程序的时候,报了一个错 auto_gamma.js:31 Uncaught DOMException: Failed to execute 'getImageData' on 'CanvasRenderingContext2D': The canvas has been tainted by cross-origin data.这是为了阻止欺骗,浏览器会追踪 imageData。当你把一个跟canvas的域不同的图片放到canvas上,这个canvas就成为 “tainted”(被污染的,脏的),浏览器就不让你操作该canvas 的任何像素。这对于阻止多种类型的XSS/CSRF攻击(两种典型的跨站攻击)是非常有用的。

解决方案:

https://github.com/mrdoob/three.js/wiki/How-to-run-things-locally我参考的这个链接里的内容,将程序运行于Server上,使图片和canvas处于同一个域,因为刚好安装了node.js的环境,所以我就直接 npm install http-server,然后再启动 http-server 加载html就可以了。


程序源代码:

基于H5/JS实现图像自动Gamma校正

猜你喜欢

转载自blog.csdn.net/u013347241/article/details/52988863