预计阅读

图像处理与 R 语言





R 语言生态中专门做图像处理的工具也不少,最流行的莫过于 magick 包。计算机视觉方面的前沿技术和理论还是卷积神经网络这套工具,什么神经网络就不说了,什么是卷积本文以简单的示例揭开其神秘面纱。像 ImageMagick 和 Adobe Photoshop 等图像处理软件,其背后都有卷积,甚至神经网络算法。

1 基础篇

R 软件内置的 graphics 包有几个读取位图的函数,如显示图片 image(),制作位图 rasterImage(),绘制位图 plot.raster()

灰度图像本质上就是一个二维矩阵,在 R 语言环境中,可由函数 as.raster() 将矩阵生成图像(颜色值),再用函数 rasterImage() 显示图像。

img <- as.raster(matrix(0:1, ncol = 5, nrow = 4, byrow = T))
img
##      [,1]      [,2]      [,3]      [,4]      [,5]     
## [1,] "#000000" "#FFFFFF" "#000000" "#FFFFFF" "#000000"
## [2,] "#FFFFFF" "#000000" "#FFFFFF" "#000000" "#FFFFFF"
## [3,] "#000000" "#FFFFFF" "#000000" "#FFFFFF" "#000000"
## [4,] "#FFFFFF" "#000000" "#FFFFFF" "#000000" "#FFFFFF"

1.1 函数 rasterImage()

函数 plot.raster() 是函数 rasterImage() 的马甲,可将函数 rasterImage() 的参数 interpolate 传递进来,它的作用是差值(平滑),效果如下图所示。

layout(mat = matrix(c(1, 2, 1, 2), 2, 2, byrow = TRUE))
plot(img, interpolate = FALSE)
plot(img, interpolate = TRUE)
用函数 `plot.raster()` 生成灰度矩阵

图 1.1: 用函数 plot.raster() 生成灰度矩阵

plot(cars)
rasterImage(img, xleft = 21, ybottom = 0, 
            xright = 25, ytop = 30, interpolate = FALSE)
用函数 `rasterImage()` 生成灰度矩阵

图 1.2: 用函数 rasterImage() 生成灰度矩阵

在绘图函数 plot() 之后,再调用 rasterImage() 函数可以实现类似图层叠加的效果。若是要在主图的右下角添加水印的话,就可以用这个函数。

library(magick)
## Linking to ImageMagick 7.1.1.44
## Enabled features: fontconfig, freetype, ghostscript, heic, lcms, raw, webp
## Disabled features: cairo, fftw, pango, rsvg, x11
## Using 11 threads
# logo 数据集来自 magick 包
logo

image_info(logo)
##   format width height colorspace matte filesize density
## 1    GIF   640    480       sRGB FALSE    28576 +72x+72
plot(cars)
rasterImage(logo, xleft = 20, ybottom = 0,
            xright = 25, ytop = 30, interpolate = FALSE)
主图添加水印

图 1.3: 主图添加水印

1.2 函数 image()

函数 image() / filled.contour() 可将数值型矩阵转化为栅格(位图)图像,以记录了地形信息的数据集 volcano 为例,下面将奥克兰火山 Maunga Whau 的海拔展示出来。

x <- 10*(1:nrow(volcano))
y <- 10*(1:ncol(volcano))
# filled.contour(x, y, volcano, color.palette = terrain.colors, axes = FALSE, ann = F)
image(x, y, volcano, axes = FALSE, ann = F, useRaster = TRUE)
奥克兰火山 Maunga Whau 的地形图

图 1.4: 奥克兰火山 Maunga Whau 的地形图

对于地形数据,R 语言社区由一个专门的 R 包 terra 来处理,它还可以处理卫星图像。

library(terra)
## terra 1.8.29
f <- system.file("ex/elev.tif", package="terra")
r <- rast(f)
plot(r)

2 进阶篇

R 语言社区中图像处理的 R 包有很多,比如 imagerimagerExtraOpenImageRmagickEBImage 等。操作矩阵即操作图像,操作图像背后其实也是操作矩阵。本文介绍 EBImage 包(Pau et al. 2010) 和 magick 包(Ooms 2024)

2.1 软件准备

EBImage 包存放在仓库BioC,下面先来安装下。

BiocManager::install("EBImage")

接着,加载这两个 R 包。

# library(EBImage)
library(magick)

查看 magick 包支持的图片格式和特性。

str(magick::magick_config())
## List of 24
##  $ version           :Class 'numeric_version'  hidden list of 1
##   ..$ : int [1:4] 7 1 1 44
##  $ modules           : logi TRUE
##  $ cairo             : logi FALSE
##  $ fontconfig        : logi TRUE
##  $ freetype          : logi TRUE
##  $ fftw              : logi FALSE
##  $ ghostscript       : logi TRUE
##  $ heic              : logi TRUE
##  $ jpeg              : logi TRUE
##  $ lcms              : logi TRUE
##  $ libopenjp2        : logi TRUE
##  $ lzma              : logi TRUE
##  $ pangocairo        : logi FALSE
##  $ pango             : logi FALSE
##  $ png               : logi TRUE
##  $ raw               : logi TRUE
##  $ rsvg              : logi FALSE
##  $ tiff              : logi TRUE
##  $ webp              : logi TRUE
##  $ wmf               : logi FALSE
##  $ x11               : logi FALSE
##  $ xml               : logi TRUE
##  $ zero-configuration: logi FALSE
##  $ threads           : int 11

2.2 读取图片

我第一次接触图像处理是本科在一本关于 Matlab 的使用教程里,当时在图书馆借阅这本书是因为正在参加全国大学生数学建模比赛,选了那道关于碎纸片复原的问题。就这样我第一次认识了计算机视觉和图像处理领域最为著名的图像 — Lenna。直到 2019 年,我才识得庐山真面目 — 刊登在杂志《花花公子》上的一张完整的照片,娱乐杂志的女星成了一大帮计算机宅男的谋女郎。

lenna <- magick::image_read(path = "img/Lenna_mini.png")
lenna

模特上半身是裸露的,侧对着镜子,脸转向读者,面带着微笑。整个环境让我想到《镖客三部曲》和《夺宝奇兵》,夕阳、草帽、风沙、浅浅的微笑一起营造一种朦胧感、含蓄美,半裸又透露出一些野性、奔放。

其实,完整的照片是这样的,摄于 1972 年。以我有限的审美,如果不看全身照,会更好。因为从构图来看,图片的焦点聚在模特的臀部,全身照透出一点色情的味道。

在摄影和美术领域,面对裸人体是磨练基本功的,历史上,刘海粟在上海美专办人体素描课是面对真人裸体的。徐悲鸿在北平艺专也是,而且也画过不少。下面分别是徐悲鸿和常书鸿(研究敦煌文化,他女儿常莎娜承父业)的作品。

2.3 卷积操作

卷积操作是图像处理中非常基础的部分,快速傅立叶变换是实现卷积操作的有效算法,R 包实现有 fftw3fftwfftwtools

本小节参考 ImageMagick 文档 Introduction to Convolution 和二维卷积操作示例 Example of 2D Convolution深度学习的卷积操作

简单起见,先考虑一维的卷积操作,即将一个序列转为另一个序列。

x <- 1:10
filter(x, filter = rep(1, 3) / 3, method = "convolution")
## Time Series:
## Start = 1 
## End = 10 
## Frequency = 1 
##  [1] NA  2  3  4  5  6  7  8  9 NA

这实际是时间序列中的一个移动平均操作,移动窗口的长度是 3 期,对新序列中的任意一个位置,它的值是原序列对应位置及其前后位置的加权平均。推广到一般的卷积,并不要求权重之和等于 1。

filter(x, filter = rep(1, 3), method = "convolution")
## Time Series:
## Start = 1 
## End = 10 
## Frequency = 1 
##  [1] NA  6  9 12 15 18 21 24 27 NA

再考虑二维的情形,类似地,在新的图片中,对给定的一个位置,它的值是原图该位置及其周围的值的加权平均,权重矩阵由核矩阵决定。考虑如下核矩阵。

\[ K = \frac{1}{4} \begin{bmatrix} 0 & 1 & 0 \\ 1 & 0 & 1 \\ 0 & 1 & 0 \end{bmatrix} \]

# 初始化核矩阵
kern <- matrix(0, ncol = 3, nrow = 3)
kern[1, 2] <- 0.25
kern[2, c(1, 3)] <- 0.25
kern[3, 2] <- 0.25
# 基础篇的示例矩阵,转成 magick 对象
img <- matrix(0:1, ncol = 5, nrow = 4, byrow = T) |> 
  as.raster() |> 
  image_read() |> 
  image_scale("300x300")
# 卷一下
img_blurred <- image_convolve(img, kernel = kern)
img_gaussian <- image_convolve(img, kernel = "Gaussian:0x4")
image_append(c(img, img_blurred, img_gaussian))

核矩阵是 \(3\times3\) 的,阶数比较小,影响范围小。再试一个例子,会更清楚些。

# 将原图缩放到像素 300x300 
img <- image_resize(logo, "300x300")
# 模糊图像的卷积操作
img_blurred <- image_convolve(img, kernel = kern)
image_append(c(img, img_blurred))

在核矩阵的作用下,图片变模糊了。不同的核函数有很多,magick 包支持的核函数如下。

kernel_types()
##  [1] "Undefined"     "Unity"         "Gaussian"      "DoG"          
##  [5] "LoG"           "Blur"          "Comet"         "Binomial"     
##  [9] "Laplacian"     "Sobel"         "FreiChen"      "Roberts"      
## [13] "Prewitt"       "Compass"       "Kirsch"        "Diamond"      
## [17] "Square"        "Rectangle"     "Disk"          "Octagon"      
## [21] "Plus"          "Cross"         "Ring"          "Peaks"        
## [25] "Edges"         "Corners"       "Diagonals"     "LineEnds"     
## [29] "LineJunctions" "Ridges"        "ConvexHull"    "ThinSe"       
## [33] "Skeleton"      "Chebyshev"     "Manhattan"     "Octagonal"    
## [37] "Euclidean"     "User Defined"

下面用2 倍的高斯模糊,看看效果。

# 高斯模糊
img_gaussian <- image_convolve(img, kernel = "Gaussian:0x2")
# 拼图
image_append(c(img, img_blurred, img_gaussian))

用2倍的高斯模糊,效果就更明显了。

# 像素 600x600
img <- image_resize(lenna, "600x600")
img_blurred <- image_convolve(img, kernel = kern)
img_gaussian <- image_convolve(img, kernel = "Gaussian:0x4")
image_append(c(img, img_blurred, img_gaussian))

前两张 Lenna 照片,看不出来差别,为什么呢?因为模糊的效果不够强,第三张照片用上4倍高斯模糊后效果就很明显了。下面再给出两个示例。

img1 <- img |> image_convolve(kernel = 'Sobel')
img2 <- img |> image_convolve(kernel = 'Sobel') |> image_negate()
img3 <- img |> image_convolve(kernel = 'DoG:0,0,2')
img4 <- img |> image_convolve(kernel = 'DoG:0,0,2') |> image_negate()
image_append(c(img1,img2,img3,img4))

2.4 图像合成

还可以使用原图和高斯模糊后的图像合成一个弱化的高斯模糊图像。合成方法采用 Blend,混合比例是原图 60% 和高斯模糊后的 40% 。

image_composite(image = img, composite_image = img_gaussian,
                operator = "Blend", compose_args = "60,40%")

以上合成命令参考 ImageMagick 文档Softened Blurring,等价于

magick face.png  -morphology Convolve Gaussian:0x4  face_strong_blur.png
magick face.png  face_strong_blur.png \
        -compose Blend -define compose:args=60,40% -composite \
        face_soft_blur.png

合成方法亦是很多的。

compose_types()
##  [1] "Undefined"        "Add"              "Atop"             "Blend"           
##  [5] "Blur"             "Bumpmap"          "ChangeMask"       "Clear"           
##  [9] "ColorBurn"        "ColorDodge"       "Colorize"         "CopyAlpha"       
## [13] "CopyBlack"        "CopyBlue"         "Copy"             "CopyCyan"        
## [17] "CopyGreen"        "CopyMagenta"      "CopyOpacity"      "CopyRed"         
## [21] "CopyYellow"       "Darken"           "DarkenIntensity"  "Difference"      
## [25] "Displace"         "Dissolve"         "Distort"          "Divide"          
## [29] "DivideDst"        "DivideSrc"        "DstAtop"          "Dst"             
## [33] "DstIn"            "DstOut"           "DstOver"          "Exclusion"       
## [37] "Freeze"           "HardLight"        "HardMix"          "Hue"             
## [41] "In"               "Intensity"        "Interpolate"      "LightenIntensity"
## [45] "Lighten"          "LinearBurn"       "LinearDodge"      "LinearLight"     
## [49] "Luminize"         "Mathematics"      "MinusDst"         "Minus"           
## [53] "MinusSrc"         "Modulate"         "ModulusAdd"       "ModulusSubtract" 
## [57] "Multiply"         "Negate"           "None"             "Out"             
## [61] "Overlay"          "Over"             "PegtopLight"      "PinLight"        
## [65] "Plus"             "Reflect"          "Replace"          "RMSE"            
## [69] "Saturate"         "Screen"           "SaliencyBlend"    "SeamlessBlend"   
## [73] "SoftBurn"         "SoftDodge"        "SoftLight"        "SrcAtop"         
## [77] "SrcIn"            "SrcOut"           "SrcOver"          "Src"             
## [81] "Stamp"            "Stereo"           "Subtract"         "Threshold"       
## [85] "VividLight"       "Xor"

3 高级篇

3.1 批处理

我在博文《我曾用过的 ImageMagick 命令》里曾使用 ImageMagick 在命令行终端中批量将 HEIC 格式图片转化为 JPG,现在有 magick 包,它将 ImageMagick 功能整个打包,这样图片转化、优化等操作都可以整合到 R 语言工作流中来了。举例来说,将目录下的 HEIC 格式图片转化为 JPG 格式,并调用无损压缩工具优化图片大小。

# HEIC 图片路径
img_heic <- list.files(path = "img", pattern = "HEIC$", full.names = T)
# JPG 图片路径
img_jpg <- sub(x = img_heic, pattern = "HEIC", replacement = "jpg")
# 格式转化
for (i in seq(length(img_heic))) {
    image_read(path = img_heic[i]) |> 
     image_write(path = img_jpg[i], format = "jpg")
}
# 压缩优化图片
xfun::tinify(input = img_jpg, output = img_jpg)

3.2 换风格(待)

模特对着镜子的画面,我在美术馆也看过一些,下面这个是油画作品,徐明华(1932-)创作的油画《镜前》。这是很印象派的人物画,可否转成现实主义风格的?

3.3 去雾霾(待)

我在北京地铁望京东站出站口对着远处的望京SOHO大厦拍摄的照片,可否将有雾霾的照片转成无雾霾的照片。连SOHO大厦的轮廓都没有了,如何复原?

视野正中的望京SOHO大厦
视野正中的望京SOHO大厦

类似地,去沙尘的图像处理办法,有没有?

望京绿地大厦
望京绿地大厦

3.4 去反光(待)

我在中国美术馆拍摄了几千张照片,凡是放在玻璃罩内的美术作品,照片都带着玻璃的反光,影响照片的二次观看效果。如何去掉图片中的灯条、人影?

丰子恺为弘一法师创作的遗像
丰子恺为弘一法师创作的遗像

4 运行环境

sessionInfo(package = c("graphics", "terra", "EBImage", "magick"))
## R version 4.4.3 (2025-02-28)
## Platform: x86_64-apple-darwin20
## Running under: macOS Sequoia 15.3.2
## 
## Matrix products: default
## BLAS:   /Library/Frameworks/R.framework/Versions/4.4-x86_64/Resources/lib/libRblas.0.dylib 
## LAPACK: /Library/Frameworks/R.framework/Versions/4.4-x86_64/Resources/lib/libRlapack.dylib;  LAPACK version 3.12.0
## 
## locale:
## [1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
## 
## time zone: Asia/Shanghai
## tzcode source: internal
## 
## attached base packages:
## [1] graphics
## 
## other attached packages:
## [1] terra_1.8-29   EBImage_4.42.0 magick_2.8.5  
## 
## loaded via a namespace (and not attached):
##  [1] cli_3.6.4         knitr_1.50        rlang_1.1.5       xfun_0.51        
##  [5] png_0.1-8         promises_1.3.2    jsonlite_1.9.1    htmltools_0.5.8.1
##  [9] httpuv_1.6.15     sass_0.4.9        methods_4.4.3     datasets_4.4.3   
## [13] rmarkdown_2.29    evaluate_1.0.3    jquerylib_0.1.4   fastmap_1.2.0    
## [17] yaml_2.3.10       lifecycle_1.0.4   utils_4.4.3       bookdown_0.42    
## [21] compiler_4.4.3    codetools_0.2-20  Rcpp_1.0.14       rstudioapi_0.17.1
## [25] base_4.4.3        stats_4.4.3       later_1.4.1       blogdown_1.21    
## [29] digest_0.6.37     R6_2.6.1          servr_0.32        magrittr_2.0.3   
## [33] bslib_0.9.0       tools_4.4.3       grDevices_4.4.3   cachem_1.1.0

5 参考文献

虽说是参考文献,EBImage 包的使用文档,我看过,但是引用论文没看过。

Ooms, Jeroen. 2024. magick: Advanced Graphics and Image-Processing in R. https://CRAN.R-project.org/package=magick.
Pau, Gregoire, Florian Fuchs, Oleg Sklyar, Michael Boutros, and Wolfgang Huber. 2010. EBImage—an R Package for Image Processing with Applications to Cellular Phenotypes.” Bioinformatics 26 (7): 979–81. https://doi.org/10.1093/bioinformatics/btq046.