我想知道是否有人能指出我正确的方向,使用javascript自动调整从手机相机拍摄的图像的亮度/对比度,以便更容易地从图像中读取文字。
感谢任何帮助,
非常感谢。
答案 0 :(得分:4)
要自动调整图像,我们可以使用从图像生成的直方图,然后使用阈值找到黑白点,用于将像素值缩放到相反端的最大值。
在HTML5中,我们需要使用canvas元素来读取像素信息。
直方图是概述哪些值在图像中最多表示。对于亮度对比度,我们会对亮度值(像素的感知亮度)感兴趣。
示例亮度直方图
要计算亮度值,我们可以使用REC.709(AKA BT.709,推荐,此处使用)或REC.601公式。
Y = 0.299 * R + 0.587 * G + 0.114 * B
我们需要将它转换为整数(iluma = Math.round(luma);
),否则我们将很难构建基于整数值[0,255]的直方图进行存储(参见下面的示例代码)。 / p>
确定使用哪个范围的策略可能会有所不同,但为简单起见,我们可以根据两端像素的最小表示选择阈值策略。
红线显示示例阈值
要根据阈值找到最暗的,我们将从左向右扫描,当我们得到高于阈值的亮度值时,将其作为最小值。如果我们到达中心(甚至只有33%),我们可以中止并默认为0。
对于最聪明的人,如果没有找到阈值,我们会做同样的但是从右到左,默认为255。
你当然可以为每一端使用不同的阈值 - 它是所有与这些值一起试错的游戏,直到找到适合你场景的东西。
我们现在应该有两个代表最小 - 最大范围的值:
基于阈值的最小 - 最大范围
首先根据最小 - 最大范围计算我们需要使用的比例因子:
scale = 255 / (max - min) * 2
我们将始终从每个组件中减去min,即使这意味着它将剪辑(如果< 0将值设置为0)。减去时,我们使用比例因子缩放每个组件值。最后的x2是为了补偿亮度和实际RGB值之间的变化。像其他人一样玩这个值(这里只是一个任意的例子)。
我们对每个像素中的每个组件(0-clip和scale)执行此操作:
component = max(0, component - min) * scale
当放回图像数据时,对比度应该是基于给定阈值的最大值。
您不必使用整个图像位图来分析直方图。如果你处理大型图像源缩小到一个小的表示 - 你不需要太多,因为我们在最亮/最暗的区域之后,而不是单个像素。
您可以使用自身的混合模式使图像变亮并添加对比度,例如multiply
,lighten
,hard-light
/ soft-light
等。(< = IE11不支持混合模式)。调整这些公式,然后进行实验。
这适用于显示上述技术的缓冲区。存在更复杂和准确的方法,但这是作为概念验证给出的(在CC-3.0-by-sa, attribution required下许可)。
它以10%的阈值开始。使用滑块可以使用阈值查看结果的差异。可以通过除此处所示之外的其他方法计算阈值。实验!
使用整页运行代码段
var ctx = c.getContext("2d"),
img = new Image; // some demo image
img.crossOrigin =""; // needed for demo
img.onload = setup;
img.src = "//i.imgur.com/VtNwHbU.jpg";
function setup() {
// set canvas size based on image
c.width = this.width;
c.height = this.height;
// draw in image to canvas
ctx.drawImage(this, 0, 0);
// keep the original for comparsion and for demo
org.src = c.toDataURL();
process(this, +tv.value);
}
function process(img, thold) { //thold = % of hist max
var width = img.width, height = img.height,
idata, data,
i, min = -1, max = -1, // to find min-max
maxH = 0, // to find scale of histogram
scale,
hgram = new Uint32Array(width); // histogram buffer (or use Float32)
// get image data
idata = ctx.getImageData(0, 0, img.width, img.height); // needed for later
data = idata.data; // the bitmap itself
// get lumas and build histogram
for(i = 0; i < data.length; i += 4) {
var luma = Math.round(rgb2luma(data, i));
hgram[luma]++; // add to the luma bar (and why we need an integer)
}
// find tallest bar so we can use that to scale threshold
for(i = 0; i < width; i++) {
if (hgram[i] > maxH) maxH = hgram[i];
}
// use that for threshold
thold *= maxH;
// find min value
for(i = 0; i < width * 0.5; i++) {
if (hgram[i] > thold) {
min = i;
break;
}
}
if (min < 0) min = 0; // not found, set to default 0
// find max value
for(i = width - 1; i > width * 0.5; i--) {
if (hgram[i] > thold) {
max = i;
break;
}
}
if (max < 0) max = 255; // not found, set to default 255
scale = 255 / (max - min) * 2; // x2 compensates (play with value)
out.innerHTML = "Min: " + min + " Max: " + max +
" Scale: " + scale.toFixed(1) + "x";
// scale all pixels
for(i = 0; i < data.length; i += 4) {
data[i ] = Math.max(0, data[i] - min) * scale;
data[i+1] = Math.max(0, data[i+1] - min) * scale;
data[i+2] = Math.max(0, data[i+2] - min) * scale;
}
ctx.putImageData(idata, 0, 0)
}
tv.oninput = function() {
v.innerHTML = (tv.value * 100).toFixed(0) + "%";
ctx.drawImage(img, 0, 0);
process(img, +tv.value)
};
function rgb2luma(px, pos) {
return px[pos] * 0.299 + px[pos+1] * 0.587 + px[pos+2] * 0.114
}
&#13;
<label>Threshold:
<input id=tv type=range min=0 max=1 step= 0.01 value=0.1></label>
<span id=v>10%</span><br>
<canvas id=c></canvas><br>
<div id=out></div>
<h3>Original:</h3>
<img id=org>
&#13;