使用JavaScript實(shí)現(xiàn)二值化圖像
黑白二值圖像的像素只有黑色和白色兩種顏色。將圖像轉(zhuǎn)換為黑白有多種用途:
- 減少文件大?。号c24位彩色圖像相比,1位黑白圖像所需的存儲(chǔ)數(shù)據(jù)通常更少。
- 圖像處理:許多圖像處理算法需要先將圖像轉(zhuǎn)換為二值圖。
- 用于顯示和打印:一些輸入/輸出設(shè)備,如激光打印機(jī)、傳真機(jī)和顯示器,只能處理二值圖。
- 美學(xué):二值圖像的像素明顯,可以算作一種像素藝術(shù)。
將圖像轉(zhuǎn)換為黑白的過(guò)程稱(chēng)為閾值處理(thresholding),通常執(zhí)行以下操作:
- 將圖像轉(zhuǎn)換為灰度
- 如果像素的灰度值小于閾值,則將該像素替換為黑色;如果大于閾值,則將該像素替換為白色
轉(zhuǎn)換為黑白的示例圖像:

在本文中,我們將討論使用JavaScript將圖像轉(zhuǎn)換為黑白二值圖的兩種方法。
- Canvas
- Dynamic Web TWAIN ,一個(gè)文檔掃描SDK
編寫(xiě)一個(gè)HTML5頁(yè)面以轉(zhuǎn)換圖像為黑白
使用以下模板創(chuàng)建一個(gè)新的HTML5頁(yè)面,然后讓我們?yōu)槠涮砑訄D像顏色轉(zhuǎn)換功能。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Convert an Image to Black and White</title>
<style>
</style>
</head>
<body>
<div class="home">
<h2>Convert an Image to Black and White</h2>
<button id="convertButton">Convert</button>
</div>
<script>
</script>
</body>
</html>
加載圖片
添加用于選擇文件的input元素,并使用按鈕觸發(fā)它。圖片將被加載到兩個(gè)img元素中,一個(gè)用于顯示轉(zhuǎn)換的結(jié)果,另一個(gè)用于存儲(chǔ)原始圖像。
HTML:
<button id="loadFileButton">Load a File</button>
<input style="display:none;" type="file" id="file" onchange="loadImageFromFile();" accept=".jpg,.jpeg,.png,.bmp" />
<div class="imageContainer">
<img id="image"/>
<img id="imageHidden"/>
</div>
<style>
.imageContainer {
max-width: 50%;
}
#image {
max-width: 100%;
}
#imageHidden {
display: none;
}
</style>
JavaScript:
function loadImageFromFile(){
let fileInput = document.getElementById("file");
let files = fileInput.files;
if (files.length == 0) {
return;
}
let file = files[0];
fileReader = new FileReader();
fileReader.onload = function(e){
document.getElementById("image").src = e.target.result;
document.getElementById("imageHidden").src = e.target.result;
};
fileReader.onerror = function () {
console.warn('oops, something went wrong.');
};
fileReader.readAsDataURL(file);
}
使用Canvas將圖像轉(zhuǎn)換為黑白
HTML5提供了一個(gè)canvas標(biāo)簽,我們可以用它操作圖像數(shù)據(jù)。我們可以使用它將圖像轉(zhuǎn)換為黑白。
向頁(yè)面添加一個(gè)隱藏的canvas元素。
<canvas id="canvasHidden"></canvas>
<style>
#canvasHidden {
display: none;
}
</style>
添加用于指定旋轉(zhuǎn)閾值的input元素。
<div class="thresholdControls" style="display:inline">
<label>
Threshold (0-255):
<input id="threshold" type="number" min="0" max="255" value="127">
</label>
</div>
將canvas的大小設(shè)置為圖像的大小。
const image = document.getElementById("image");
const canvas = document.getElementById("canvasHidden");
canvas.width = image.naturalWidth;
canvas.height = image.naturalHeight;
獲取canvas的context以執(zhí)行操作。
const context = canvas.getContext("2d");
將圖像繪制到canvas上。
context.drawImage(image, 0, 0);
獲取圖像的ImageData:
const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
ImageData以RGBA順序?qū)⑾袼刂荡鎯?chǔ)在Uint8ClampedArray中,取值范圍是0到255。像素從左上角到右下角逐行排列。
創(chuàng)建一個(gè)函數(shù)以根據(jù)RGB值計(jì)算灰度值。
//https://github.com/image-js/image-js/blob/9ab86a86f6c13a9a7d14c62566c1396c3c6f54f4/src/image/transform/greyAlgorithms.js
function RGBToGrayScale(red,green,blue){
//return red * 0.2126 + green * 0.7152 + blue * 0.0722;
return (red * 6966 + green * 23436 + blue * 2366) >> 15;
}
計(jì)算灰度值的方法有很多。這里,我們使用Rec. 709亮度系數(shù)進(jìn)行轉(zhuǎn)換。使用基于整數(shù)的乘法計(jì)算和移位優(yōu)化了計(jì)算速度。
創(chuàng)建一個(gè)函數(shù)以根據(jù)閾值確定像素值應(yīng)該為黑色還是白色。
//return true if the value should be black. return false if the value should be white
function threshold(grayscale){
const thresholdValue = parseInt(document.getElementById("threshold").value);
if (grayscale < thresholdValue) {
return true;
}else{
return false;
}
}
遍歷所有像素,將RGB值設(shè)置為我們計(jì)算出的黑白值,黑色是0,白色是255。
const pixels = imageData.data; //[r,g,b,a,...]
for (var i = 0; i < pixels.length; i += 4) {
const red = pixels[i];
const green = pixels[i + 1];
const blue = pixels[i + 2];
const grayscale = RGBToGrayScale(red, green, blue)
if (threshold(grayscale)) {
pixels[i] = 0;
pixels[i + 1] = 0;
pixels[i + 2] = 0;
}else{
pixels[i] = 255;
pixels[i + 1] = 255;
pixels[i + 2] = 255;
}
}
放回ImageData。
context.putImageData(imageData, 0, 0);
顯示轉(zhuǎn)換后的圖像。
image.src = canvas.toDataURL();
使用OTSU方法確定閾值
我們可以使用OTSU方法自動(dòng)確定分離背景和前景的最佳閾值。
在頁(yè)面中包含以下JS文件。
//https://github.com/cawfree/otsu/
const histo = (data, bins) =>
data.reduce((arr, e) => {
arr[bins.indexOf(e)] += 1;
return arr;
}, [...Array(bins.length)].fill(0));
const width = (hist, s, e) => {
let v = 0;
for (let i = s; i < e; i += 1) {
v += hist[i];
}
return v;
};
const bins = data => Array.from(new Set(data)).sort((e0, e1) => e0 - e1);
const weight = (hist, s, e, total) => {
let v = 0;
for (let i = s; i < e; i += 1) {
v += hist[i];
}
return v / total;
};
const mean = (hist, bins, s, e, width) => {
let v = 0;
for (let i = s; i < e; i += 1) {
v += hist[i] * bins[i];
}
return v * width;
};
const variance = (hist, bins, s, e, mean, width) => {
let v = 0;
for (let i = s; i < e; i += 1) {
const d = bins[i] - mean;
v += d * d * hist[i];
}
return v * width;
};
const cross = (wb, vb, wf, vf) => wb * vb + wf * vf;
const otsu = (data) => {
const b = bins(data);
const h = histo(data, b);
const { length: total } = data;
const vars = [...Array(b.length)].map((_, i) => {
const s0 = 0;
const e0 = i;
const s1 = i;
const e1 = h.length;
const w0 = 1 / width(h, s0, e0);
const w1 = 1 / width(h, s1, e1);
const wb = weight(h, s0, e0, total);
const vb = variance(h, b, s0, e0, mean(h, b, s0, e0, w0), w0);
const wf = weight(h, s1, e1, total);
const vf = variance(h, b, s1, e1, mean(h, b, s1, e1, w1), w1);
const x = cross(wb, vb, wf, vf);
return !isNaN(x) ? x : Number.POSITIVE_INFINITY;
});
return b[vars.indexOf(Math.min(...vars))];
};
使用OTSU計(jì)算閾值。我們需要將灰度直方圖傳遞給otsu函數(shù)。
function calculateThresholdWithOTSU(){
const image = document.getElementById("imageHidden");
const canvas = document.getElementById("canvasHidden");
const width = image.naturalWidth;
const height = image.naturalHeight;
const context = canvas.getContext('2d');
canvas.width = width;
canvas.height = height;
context.drawImage(image, 0, 0);
const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
const pixels = imageData.data; //[r,g,b,a,...]
const grayscaleValues = [];
for (var i = 0; i < pixels.length; i += 4) {
const red = pixels[i];
const green = pixels[i + 1];
const blue = pixels[i + 2];
const grayscale = RGBToGrayScale(red, green, blue)
grayscaleValues.push(grayscale);
}
document.getElementById("threshold").value = otsu(grayscaleValues);
}
使用Dynamic Web TWAIN將圖像轉(zhuǎn)換為黑白
Dynamic Web TWAIN是一個(gè)文檔掃描SDK,可以在瀏覽器中掃描文檔。它提供了各種圖像處理方法。我們可以使用其ConvertToBW方法轉(zhuǎn)換圖像為黑白。
使用它的優(yōu)點(diǎn)是,它可以用于批量處理大量圖像,因?yàn)樘幚硎鞘褂帽镜剡M(jìn)程完成的。由于使用了libpng等本地圖像庫(kù)根據(jù)顏色信息輸出文件,輸出文件的尺寸也會(huì)更優(yōu)。
以下是使用它的步驟:
在頁(yè)面中引入Dynamic Web TWAIN。
<script src="https://unpkg.com/dwt@18.4.2/dist/dynamsoft.webtwain.min.js"></script>
初始化一個(gè)Web TWAIN的實(shí)例并使用它來(lái)轉(zhuǎn)換圖像??梢栽?a target="_blank">此處申請(qǐng)其許可證。
let DWObject;
Dynamsoft.DWT.AutoLoad = false;
Dynamsoft.DWT.ProductKey = "DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAxLTE2NDk4Mjk3OTI2MzUiLCJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSIsInNlc3Npb25QYXNzd29yZCI6IndTcGR6Vm05WDJrcEQ5YUoifQ=="; //one-day trial
Dynamsoft.DWT.ResourcesPath = "https://unpkg.com/dwt@18.4.2/dist";
async function convertWithDWT(){
if (!DWObject) {
await initDWT();
}
DWObject.RemoveAllImages();
let response = await fetch(document.getElementById("imageHidden").src);
let buffer = await response.arrayBuffer();
DWObject.LoadImageFromBinary(buffer,
function(){
const convert = async () => {
const thresholdValue = parseInt(document.getElementById("threshold").value);
await DWObject.RunCommandAsync({command:"convertToBW",parameter:{index:0,threshold:thresholdValue}});
document.getElementById("image").src = DWObject.GetImageURL(0);
}
convert();
},
function(errorCode, errorString){
console.log(errorString);
})
}
function initDWT(){
return new Promise((resolve, reject) => {
const title = document.querySelector("h2").innerText;
document.querySelector("h2").innerText = "Loading Dynamic Web TWAIN...";
Dynamsoft.DWT.CreateDWTObjectEx(
{
WebTwainId: 'dwtcontrol'
},
function(obj) {
DWObject = obj;
document.querySelector("h2").innerText = title;
resolve();
},
function(err) {
console.log(err);
document.querySelector("h2").innerText = "Failed to load Dynamic Web TWAIN";
reject(err);
}
);
})
}
源代碼
可以在以下倉(cāng)庫(kù)中找到所有代碼和在線(xiàn)演示:
github.com/tony-xlh/Color-Conversion-JavaScript
到此這篇關(guān)于使用JavaScript實(shí)現(xiàn)二值化圖像的文章就介紹到這了,更多相關(guān)JavaScript二值化圖像內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JavaScript面試中??嫉淖址僮鞣椒ù笕?包含ES6)
對(duì)于JavaScript字符串操作方法,你真的全部掌握了嗎?來(lái)看看這篇面試中??嫉淖址僮鞔笕?,包含最新的ES6字符串操作方法,值得收藏哦2020-05-05
利用js正則表達(dá)式驗(yàn)證手機(jī)號(hào),email地址,郵政編碼
利用js正則表達(dá)式驗(yàn)證手機(jī)號(hào),email地址,郵政編碼。需要的朋友可以過(guò)來(lái)參考下,希望對(duì)大家有所幫助2014-01-01
頁(yè)面只能打開(kāi)一次Cooike如何實(shí)現(xiàn)
由于WEBIM處在獨(dú)立頁(yè)面,所以如果多次點(diǎn)擊就會(huì)出現(xiàn)多個(gè)頁(yè)面,本文將介紹詳細(xì)的解決方法,需要了解的朋友可以參考下2012-12-12
javascript Promise簡(jiǎn)單學(xué)習(xí)使用方法小結(jié)
下面小編就為大家?guī)?lái)一篇javascript Promise簡(jiǎn)單學(xué)習(xí)使用方法小結(jié)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-05-05
js類(lèi)定義函數(shù)時(shí)用prototype與不用的區(qū)別示例介紹
沒(méi)有使用prototype的方法相當(dāng)于類(lèi)的靜態(tài)方法,相反,使用prototype的方法相當(dāng)于類(lèi)的實(shí)例方法,不許new后才能使用2014-06-06
js給網(wǎng)頁(yè)加上背景音樂(lè)及選擇音效的方法
這篇文章主要介紹了js給網(wǎng)頁(yè)加上背景音樂(lè)及選擇音效的方法,涉及javascript操作音頻的技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-03-03
詳解JavaScript中關(guān)于this指向的4種情況
這篇文章主要介紹了JavaScript中關(guān)于this指向的4種情況,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04
Js+Dhtml:WEB程序員簡(jiǎn)易開(kāi)發(fā)工具包(預(yù)先體驗(yàn)版)
Js+Dhtml:WEB程序員簡(jiǎn)易開(kāi)發(fā)工具包(預(yù)先體驗(yàn)版)...2006-11-11
JS實(shí)現(xiàn)的拋物線(xiàn)運(yùn)動(dòng)效果示例
這篇文章主要介紹了JS實(shí)現(xiàn)的拋物線(xiàn)運(yùn)動(dòng)效果,結(jié)合實(shí)例形式分析了javascript拋物線(xiàn)運(yùn)動(dòng)的相關(guān)運(yùn)算與元素動(dòng)態(tài)操作實(shí)現(xiàn)技巧,需要的朋友可以參考下2018-01-01

