背景介绍
在开发过程中,遇到这样一个问题,热敏打印机打印图片,需要将图片进行缩放、二值化处理后,将图片像素数据发送给打印机进行打印,最初,是基于canvas
绘制图片,对图片进行缩放处理,这种方式虽然也能达到目的,但是我觉得并不完美,我觉得一定有办法,能将原始图像的像素矩阵,进行各种运算后得到想要的效果,比如缩放、裁剪、灰度化等,于是有了对图像进阶了解的探索之路。
图像的表现形式
在计算机中,位图是用像素来展示图片的,也就是将图片分割为很小很小的单元格,每个单元格存储一个色值,这些单元格按照特定顺序排列在一起,也就形成了一张图片,这也就是为什么,像素越高(指定尺寸内的单元格越多),图像看起来就约细腻。
计算机中,颜色由RGB
三种颜色混合而成,那么一个像素点的色值就会包含三个数据,即红色R
,绿色G
,蓝色B
,然而通常使用的,还会包含透明度(Alpha通道
,也就是RGBA
),因此一个像素就变成了四个数值来表示,每个值用一个字节(8位)存储,那么每个值的范围是0-255
,所以RGB
图像能表现的颜色值共有256*256*256=16777216
种。
图像缩放原理
明白了上述的图像表现原理,就容易知道,对图片的各种变化,其实就是对图片像素数据的运算。
图片缩小
图片缩小原理是,按照比例,将像素使用特定算法进行合并,得到一个新的像素矩阵,即实现了图片的缩小,如下图所示(一个格子表示一个像素),左边表示一张8*8
大小的图片,需要缩小到4*4
大小,就需要将右图中每个单元格的数据使用算法进行合并,得到一个像素值。
图片放大
明白了图片缩小原理,图片放大就容易理解了,既然缩小是减少数据,那放大就是增加数据,通过算法,产生新的像素数据进行排列,就得到了放大后图片的像素矩阵。
对于图片缩放的像素运算算法,这里不做讲解,有兴趣可参考博文【图像缩放】双立方(三次)卷积插值。了解图片缩放的原理后,图片的裁剪、旋转等操作也都类似,都可以基于图片像素矩阵进行运算后得到新图片的像素矩阵。
彩色图片转灰度图
灰度图定义
任何颜色都由红、绿、蓝三原色组成,而灰度图只有一个通道,他有256个灰度等级,255代表全白,0表示全黑。
RGB图转灰度图
我们知道RGB
颜色由三个字节存储,而灰度图又只有一个字节存储色值,如果要将RGB
图像转换为灰度图,我们可以通过算法对RGB
进行计算,得到灰阶值,然后将RGB
都改为灰阶值,所有像素通过如此计算,图片就变成了灰度图。
计算灰阶值通常有如下算法:
1.浮点法:Gray=R*0.3+G*0.59+B*0.11
2.整数法:Gray=(R*30+G*59+B*11) / 100
3.移位法:Gray=(R*77+G*151+B*28)>>8
4.平均值法:Gray=(R+G+B)/ 3
5.仅取绿色:Gray=G
上面的系数是怎么来的呢,因为人眼对红绿蓝三种颜色的感知程度不同,因此对三种颜色以不同的权重进行计算,得到的灰阶值会更符合人眼观看。但是每种算法都有它的优缺点。
代码示例
下面我们使用HTML
语法演示彩色图片转灰阶图;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>图片处理</title> <style> #group { display: flex; width: 700px; justify-content: space-between; }
.item { width: 300px; height: 300px; border: 1px solid #000; }
#canvas { border: 1px dashed #000; } </style> </head> <body> <input type="file" name="image" id="imgFile" onchange="loadImg()"> <input type="button" value="灰阶图" onclick="toGray()"> <div id="group"> <div class="scream item"> <img alt="loading.." width="300" id="scream"> </div> <canvas id="canvas" width="300" height="300" class="item"></canvas> </div>
<script>
var c_w = 300, c_h = 300
function loadImg () { var img = getEleById('scream') var file = getEleById('imgFile').files[0] if (!/image\/\w+/.test(file.type)) { alert('文件必须为图片!') return false }
var reader = new FileReader() reader.addEventListener('load', function () { img.src = this.result }) if (file) { reader.readAsDataURL(file) loadCanvas() } console.log(file) }
function loadCanvas () { var canvas = getEleById('canvas') var ctx = canvas.getContext('2d') var img = getEleById('scream') img.onload = function () { ctx.clearRect(0, 0, c_w, c_h) ctx.drawImage(img, 0, 0, img.width, img.height) } }
function getEleById (id) { return document.getElementById(id) }
function toGray () { var img = getEleById('scream') var canvas = getEleById('canvas') var ctx = canvas.getContext('2d') ctx.clearRect(0, 0, c_w, c_h) ctx.drawImage(img, 0, 0, img.width, img.height)
var orignImgData = ctx.getImageData(0, 0, img.width, img.height) var pixelArr = orignImgData.data var len = pixelArr.length
for (var i = 0; i < len; i += 4) { var R = pixelArr[i] var G = pixelArr[i + 1] var B = pixelArr[i + 2]
var grayValue = R * 0.3 + G * 0.59 + B * 0.11 pixelArr[i] = grayValue pixelArr[i + 1] = grayValue pixelArr[i + 2] = grayValue } ctx.putImageData(orignImgData, 0, 0) } </script> </body> </html>
|
通过上图可以看到,基于人眼感知权重分配计算的灰度图,其他算法可自行尝试对比。
RGB彩图二值化
什么是二值化
上面我们了解了灰度图,一个像素由一个字节表示,可表示256
个色值,二值化图片,就是把像素改为1位
表示,即像素点只有黑(1)与白(0)两种颜色,因此彩图二值化的流程应该是彩图 -> 灰度图 -> 二值化
。二值化图像有它实用的场景,比如扫描文档时,背景会有浅灰色的杂色,我们可以使用二值化处理图片,让图片变成只有黑白两色,又比如,热敏打印机只能打印黑白两色,我们想要在热敏打印机中打印图片,就只能将图片二值化处理后才能打印。
二值化实现
彩色图片二值化,首先需要将图片转为灰度图,将原来一个像素由3个字节存储变为一个像素由一个字节存储(灰度图0为全黑色,255为全白色),然后根据阈值判断这个灰度图的像素是黑色还是白色,比如设置阈值是150,一个灰度图的像素值是132,150 > 132,因此这个像素点二值化后为0,即这个点是黑色。
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>图片二值化</title> <style> #group { display: flex; width: 700px; justify-content: space-between; }
.item { width: 300px; height: 300px; border: 1px solid #000; }
#canvas { border: 1px dashed #000; } </style> </head> <body> <input type="file" name="image" id="imgFile" onchange="loadImg()"> <input type="text" name="Threshold " value="150" id="Threshold"> <input type="button" value="二值化" onclick="binary()"> <div id="group"> <div class="scream item"> <img alt="loading.." width="300" id="scream"> </div> <canvas id="canvas" width="300" height="300" class="item"></canvas> </div>
<script>
var c_w = 300, c_h = 300
function loadImg () { var img = getEleById('scream') var file = getEleById('imgFile').files[0] if (!/image\/\w+/.test(file.type)) { alert('文件必须为图片!') return false }
var reader = new FileReader() reader.addEventListener('load', function () { img.src = this.result }) if (file) { reader.readAsDataURL(file) loadCanvas() } console.log(file) }
function loadCanvas () { var canvas = getEleById('canvas') var ctx = canvas.getContext('2d') var img = getEleById('scream') img.onload = function () { ctx.clearRect(0, 0, c_w, c_h) ctx.drawImage(img, 0, 0, img.width, img.height) } }
function getEleById (id) { return document.getElementById(id) }
function binary () { var Threshold = getEleById('Threshold').value var img = getEleById('scream') var canvas = getEleById('canvas') var ctx = canvas.getContext('2d') ctx.clearRect(0, 0, c_w, c_h) ctx.drawImage(img, 0, 0, img.width, img.height)
var orignImgData = ctx.getImageData(0, 0, img.width, img.height) var pixelArr = orignImgData.data var len = pixelArr.length
for (var i = 0; i < len; i += 4) { var R = pixelArr[i] var G = pixelArr[i + 1] var B = pixelArr[i + 2]
var grayValue = R * 0.3 + G * 0.59 + B * 0.11 var binaryValue = grayValue > Threshold ? 255 : 0 pixelArr[i] = binaryValue pixelArr[i + 1] = binaryValue pixelArr[i + 2] = binaryValue } ctx.putImageData(orignImgData, 0, 0) } </script> </body> </html>
|
可以看到,阈值的选定对二值化后的图片有着较大影响,因此,为了得到更好的二值化图片,通常会使用算法优化,动态改变阈值。