> 本文摘自《Keras深度学习:入门、实战及进阶》第四章部分章节。
## 什么是EBImage
EBImage是R的一个扩展包,提供了用于读取、写入、处理和分析图像的通用功能,非常容易上手。EBImage包在Bioconductor中,通过以下命令进行安装。
```r
install.packages("BiocManager")
BiocManager::install("EBImage")
```
EBImage安装后,可以通过以下命令将其加载到R中。
```r
library("EBImage")
```
### 1. 图像读取与保存
EBImage的基本功能包括图像的读取、显示和写入。使用readImage()函数读取图像,函数中的参数files表示需要读取的文件名或URL,参数type表示读取的图像文件格式,目前支持jpeg、png和tiff三种图像文件格式。
首先,将我们将一张图像格式为jpg文件的灰色图像加载到R中。我们可以通过display()函数对刚刚加载的图像进行可视化
```r
> img <- readImage('../images/cat.jpg')
> display(img ,method = 'browser')
```
![image.png](https://s2.51cto.com/images/20220209/1644412207670805.png?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=)
当display()函数的参数method为“browser”时,在R中运行命令后将在默认Web浏览器中打开图像,在RStudio中运行命令后将在View窗口打开可交互的图像。使用鼠标或者键盘快捷键可以放大或缩小图像、平移或循环显示多个图像。当参数method为“raster”时,就在当前设备上绘制静态图像,我们还可以利用R的低级绘图函数在图像上添加其他元素。运行以下程序代码将在图像上添加文本标签
```r
> display(img,method = 'raster')
> text(x = 20,y = 20,label = 'cat',adj = c(0,1),col = 'orange',cex = 2)
```
![image.png](https://s2.51cto.com/images/20220209/1644412292786997.png?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=)
上面示例读入的是黑白图像(或称灰色图像),readImage()和display()函数也可以轻松读入彩色照片。
```r
> imgcol <- readImage('../images/cat-color.jpg')
> display(imgcol,method = 'raster')
```
![image.png](https://s2.51cto.com/images/20220209/1644412382432797.png?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=)
### 2.色彩管理
colorMode()函数可用于访问和更改此属性,修改图像的渲染模式。在下一个例子中,我们将一张彩色图像的模式变为灰色(Grayscale),那么该图像将不再显示单一的彩色图像,而是转换为三帧的灰色图像,分别对应红、绿、蓝三个通道。colorMode()函数只会改变EBImage渲染图像的方式,并不会改变图像的内容。运行以下程序代码,将一个彩色图像渲染为一个具有3帧(红色通道、绿色通道、蓝色通道)的灰色图像。
```r
> colorMode(imgcol) <- Grayscale
> display(imgcol,method = 'raster',all = TRUE,nx = 3)
```
![image.png](https://s2.51cto.com/images/20220209/1644412453752048.png?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=)
我们可以使用更灵活的channel()函数进行色彩空间转换,可以将灰色图像转换为彩色图像,也可以从彩色图像中提取颜色通道。与colorMode()函数不同,channel()函数还可以更改图像的像素强度值。asred、asgreen和asblue转换模式可以将灰色图像或数组转换为指定色调的彩色图像,此时图形数据也将从二维变成三维。
```r
> img_asgreen <- channel(img,'asgreen')
> dim(img)
[1] 1920 1080
> dim(img_asgreen)
[1] 1920 1080 3
```
![image.png](https://s2.51cto.com/images/20220209/1644412489789862.png?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=)
### 3.图像处理
作为数值数组,可以使用R的任何算数运算符方便地操作图像。例如,我们可以通过简单地利用其最大值减去图像数据来生成负图像。
```r
> img_neg <- max(img) - img
> img_comb <- combine(img,img_neg) TRUE)
```
![image.png](https://s2.51cto.com/images/20220209/1644412551207219.png?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=)
我们还可以通过加法来增加图像的亮度,通过相乘来调整对比度,以及通过求幂来应用伽玛校正。
```r
> img_comb1 <- combine(
+ img,
+ img + 0.3,
+ img * 2,
+ img ^ 0.5
+ )
> display(img_comb1,method = 'raster',all=TRUE)
```
![image.png](https://s2.51cto.com/images/20220209/1644412588863337.png?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=)
我们可以使用标准矩阵的子集选取方式对图像进行裁剪。比如我们通过选取Image类的部分数据用于绘制猫咪的头像
```r
> img_crop <- img[800:1700, 100:950]
> plot(img_crop)
```
![image.png](https://s2.51cto.com/images/20220209/1644412613170971.png?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=)
### 4.空间变换
对于灰色图像,可以使用R基础包的t()函数或者EBImage扩展包的transpose()函数进行转置。
```r
> img_t <- transpose(img) # 等价于 img_t <- t(img)
> plot(img_t)
```
![image.png](https://s2.51cto.com/images/20220209/1644412659972205.png?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=)
对于彩色图像,我们不能使用t()函数,而是需要使用transpose()函数对其进行转置,其能通过交换空间维度来置换图像。
```r
> t(imgcol) # 报错
Error in t.default(imgcol) : argument is not a matrix
> imgcol_t <- transpose(imgcol)
> plot(imgcol_t)
```
![image.png](https://s2.51cto.com/images/20220209/1644413434951418.png?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=)
除了转置,我们还有更多关于图像的空间变换,例如平移、旋转、反射和缩放。translate()函数通过指定的二维向量移动图像平面,裁剪图像区域外的像素,并将进入图像区域的像素设置为背景。参数v是由两个数字组成的向量,表示以像素为单位的平移向量。以下代码实现将图像往右移动100像素,往上移动50像素。
```r
> img_rotate <- rotate(img,30)
> plot(img_rotate)
```
![image.png](https://s2.51cto.com/images/20220209/1644413467894970.png?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=)
使用resize()函数可以将图像进行缩放,如果仅提供宽度或者高度之一,则将自动计算另一个尺寸并保持原始宽高比。以下代码实现将img、imgcol图像的宽、高均设置为256。
```r
> # 调整图像尺寸
> img_resize <- resize(img,w = 256,h = 256)
> imgcol_resize <- resize(imgcol,w = 256,h = 256)
> par(mfrow=c(1,2))
> plot(img_resize)
> plot(imgcol_resize)
> par(mfrow=c(1,1))
```
![image.png](https://s2.51cto.com/images/20220209/1644413509873771.png?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=)
flip()和flop()函数分别围绕水平轴和垂直轴反射图像。
```r
> img_flip <- flip(img)
> img_flop <- flop(img)
> display(combine(img_flip, img_flop),
+ all=TRUE,method = 'raster')
```
![image.png](https://s2.51cto.com/images/20220209/1644413534283988.png?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=)
affine()函数可以实现空间线性变换,其中像素坐标(用矩阵px表示)转换为cbind(px, 1)%*%m。
```r
> m <- matrix(c(1,-.5,128,0,1,0),nrow=3,ncol=2)
> img_affine <- affine(img, m)
> display(img_affine)
```
![image.png](https://s2.51cto.com/images/20220209/1644413562820872.png?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=)
### 5.形态运算
二值图像是仅包含两组像素的图像,其值为0和1,分别表示背景像素和前景像素。这样的图像要经历几种非线性的形态学运算:侵蚀、膨胀、打开和闭合。这些操作通过以下方式在二进制图像上覆盖一个称为结构元素的掩码来工作:
+ 腐蚀:对于每个前景像素,在其周围放置一个遮罩,如果遮罩覆盖的任何像素来自背景,请将其设置为背景。
+ 膨胀:对于每个背景像素,在其周围放置一个蒙版,如果蒙版覆盖的任何像素都来自前景,请将像素设置为前景。
我们首先读入一个二值图像,然后利用markBrush()函数创建一个形状为diamond,大小为3的滤波器,再通过erode()函数对二值图像进行腐蚀、dilate()函数对图像进行膨胀。
```r
> shapes <- readImage('../images/shapes.png')
> kern = makeBrush(3, shape='diamond')
> shapes_erode= erode(shapes, kern) # 腐蚀
> shapes_dilate = dilate(shapes, kern) # 膨胀
> display(combine(shapes,shapes_erode, shapes_dilate),
+ all=TRUE,method = 'raster',nx = 3)
```
![image.png](https://s2.51cto.com/images/20220209/1644413638390315.png?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=)
### 6.图像分割
图像分割是指对图像进行分割,通常用于识别图中的对象。非接触连接的对象可以使用bwlabel()函数进行分段,而watershed()与propagate()函数能够用更复杂的算法分离彼此接触的对象。
bwlabel()函数查找除背景以外的每个相连像素集,并用唯一递增的整数重新标记这些集。可以在带阈值的二进制图像上调用它以提取对象。
```r
> shapes_label <- bwlabel(shapes)
> table(shapes_label)
shapes_label
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
16821 398 129 135 11 147 81 109 60 87 93 81 78 100 87 15
> max(shapes_label)
[1] 15
```
shapes_label图像的像素值范围从0(对应于背景)到其包含的对象数,最大值为15。
我们利用normalize()函数对其进行(0,1)范围内的标准化,这将导致不同的对象以不同的灰色阴影渲染
```r
> display(normalize(shapes_label),method = 'raster') # 灰色渲染
```
可视化分割的另一种方式是使用colorLabels()函数,该函数通过唯一颜色的随机排列对对象进行颜色编码。
```r
> display(colorLabels(shapes_label),method = 'raster') # 彩色渲染
```
![image.png](https://s2.51cto.com/images/20220209/1644413732302142.png?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=)
EBImage将对象掩码定义为具有相同唯一整数值的一组像素。通常,包含对象掩码的图像就是分割函数的结果,如bwlabel、watershed或propagate。通过rmObject()函数可以将对象从这些图像中删除,只需将对象的像素值设置为0就可以从掩码中删除对象。默认情况下,在删除对象之后,所有剩余的对象都会被重新标记,以便最高对象ID对应于掩码中的对象数量。参数reenumerate可用于更改此行为并保留原始对象id。
我们需要将Rstudio_Keras对象中的“_”移除,通过以下程序代码实现。
```r
> z <- rmObjects(shapes_label,15) # 移除"_"
> display(z,method = 'raster')
```
![image.png](https://s2.51cto.com/images/20220209/1644413767537623.png?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=)
![image.png](https://s2.51cto.com/images/20220209/1644413856928999.png?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=)
![image.png](https://s2.51cto.com/images/20220209/1644413919257575.png?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=)