如何在R

时间:2016-03-05 14:25:27

标签: r image bitmap rotation

问题:

这与图像数据的MNIST呈现有关。我想对图像进行精细旋转。所有图像的集合表示为data.frame,其中每行(每个图像)是785个字符向量;第一个字符是数字的值;剩下的784是一个扁平的28x28像素矩阵。

我想有效地旋转784个字符以生成一些额外的旋转图像;即我想设计一些新数据!

我已经使用以下代码完成了此操作,但我现在要做的是提高效率。目前,使用2.7GHz i7机器上的6个线程,在42,000张图像上运行需要数小时。

####################################################################################################
# rotateImage() -- generates new images rotated from -angleDegree tp +angleDegree
#                  images will be returned as a data.frame
####################################################################################################
xOffset = 14
yOffset = 14
rotateImage <- function(imgData,angleDegree) {
  for (t in -angleDegree:angleDegree) {
    newImgData <- t(as.data.frame(rep(0,785)))
    newImgData[1,1]=imgData[1,1] # set the actual digit value to imgData value
    for (j in 1:28) {
      for (i in 1:28) {
        newI = round(xOffset + (((i-xOffset)*cos(t*pi/180)) + ((j-yOffset)*sin(t*pi/180))))
        newJ = round(yOffset + (((xOffset-i)*sin(t*pi/180)) + ((j-yOffset)*cos(t*pi/180))))
        if ((newI %in% 1:28) && (newJ %in% 1:28)) {
          newImgData[1,1 + newI + ((newJ-1)*28)] = imgData[1,1+i+((j-1)*28)]
        }
      }
    }
    if (exists("retImages")) {
      retImages <- rbind(retImages,newImgData)
    } else {
      retImages = newImgData
    }
  }
  retImages
}
####################################################################################################
# various globals
degreesToRotate = 5
# one sample image
sampleData = t(as.data.frame(c(8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,34,34,0,0,0,57,136,162,245,203,19,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,47,147,249,253,224,232,232,6,81,21,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,91,254,253,242,128,17,97,240,149,254,115,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,57,253,254,151,38,0,0,47,253,253,228,40,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,13,221,254,160,0,0,0,0,164,254,228,102,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,47,253,253,9,0,0,17,130,251,223,73,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,47,253,253,17,0,19,199,254,223,42,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,13,186,253,235,101,199,253,195,40,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,17,210,255,254,254,228,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,9,254,253,253,211,17,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,11,149,254,215,232,253,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,125,253,228,15,107,253,184,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,106,254,228,0,0,17,235,229,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,130,243,164,15,0,0,13,222,241,19,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,119,254,206,9,0,0,0,141,253,142,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,17,234,228,40,0,0,0,102,240,219,25,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,141,254,102,0,0,0,128,245,188,25,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,207,168,0,9,89,172,254,160,21,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,207,169,83,174,242,230,80,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,140,253,228,143,38,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)))
# image rotated from -5 to 5 degrees:
rotations <- rotateImage(sampleData,degreesToRotate)
# plot the images to verify correctness
library(raster)
bw=grey.colors(2,start=0,end=1)
for (i in 1:nrow(rotations)) {
  pixData <- rotations[i,2:785]
  dim(pixData) <- c(28,28)
  image(pixData, col=bw)
}

基本上这是从关闭所有像素开始的(所以剪切不是麻烦 - 我正在做精细旋转,而不是45度的东西),并且在两个循环中,任何“开”像素将被移动到新的位置对于旋转的图像。 (顺便说一句,这是在原始图像的同一“帧”内在R中进行精细角度图像旋转的简单方法。)

我想用一些运行得更快的花哨的单线转换来消除i和j循环。任何有关这些方面的提示或其他新颖的想法都将不胜感激。

解决方案:

一个已接受的答案(参见下面的@ 42-),利用TsparseMatrix;并在此处略微修改为函数:

####################################################################################################
xOffsetM = 13
yOffsetM = 13
rotateImageM <- function(imgData,angleDegree) {
  for (t in -angleDegree:angleDegree) {
    rotMat <- matrix( c( cos(t*pi/180), sin(t*pi/180), -sin(t*pi/180), cos(t*pi/180)), 2, 2)
    M <- as(matrix(imgData[,2:785], 28, 28), "TsparseMatrix")
    idxs <- round(cbind(M@i - xOffsetM, M@j - yOffsetM) %*%  rotMat)
    M@i <- as.integer(idxs[,1]+xOffsetM)  # back shift to right and ..
    M@j <- as.integer(idxs[,2]+yOffsetM)  # back "up"
    newImgData <- imgData
    newImgData[,2:785] <- t(as.data.frame(as.vector(M)))
    if (exists("retImages")) {
      retImages <- rbind(retImages,newImgData)
    } else {
      retImages <- newImgData
    }
  }
  retImages
}

对于其他对这类问题感到好奇的人,你可能会看到图像尺寸(28x28)是任意的 - 特定于我正在研究的例子;这些也决定了解决方案中“偏移”选择为14或13。因此,这应该很容易扩展到任何大小的图像。

在一天结束时......

所提出的解决方案都有效,但是对于大量图像来说仍然有点慢或内存效率低下。我最终在VB.Net中编写了一个简单的应用程序(我很清楚,没有其他理由与其他任何东西相比),它处理了42,000个图像,使用原始版本生成11次基于10次旋转的结果{1}}接近...并在6分钟内完成,包括对非SSD磁盘的所有读/写。我仍然不确定为什么我不能让R以大致相同的效率翻转所有内容。

2 个答案:

答案 0 :(得分:1)

这展示了处理sparseMatrix表示的策略:

M <-  as( matrix( sampleData[-1], 28), "TsparseMatrix")
 idxs <- floor( cbind( M@i-13, M@j-13) %*%   
      # This implicitly shifts the rows down and columns left by 14 since sparse i-j's are 0-based
             matrix( c(cos(5*2*pi/360), sin(5*2*pi/360), 
                       -sin(15*2*pi/360), cos(5*2*pi/360)), 2,2) ) # 5 degrees
 M@i <- as.integer( idxs[,1]+14)  # back shift to right and ..
 M@j=as.integer( idxs[,2]+14)     # back "up"
 image( matrix(sampleData[-1], 28) )

enter image description here

 image( as.matrix(M) )

enter image description here

答案 1 :(得分:1)

另一种方法是使用Bioconductor软件包提供的功能 EBImage ,这是 R 的图像处理和分析工具箱。要安装软件包,请使用:

source("http://bioconductor.org/biocLite.R")
biocLite("EBImage")

然后,您可以将图像数据表示为Image对象,并使用rotate函数对其进行转换。这由以下代码说明。

如果原始像素数据存储在数据框中,则首先需要将其转换为 EBImage 使用的基于数组的格式。

library("EBImage")

# sample data.frame containg 3 copies of sampleData
df = rbind(sampleData, sampleData, sampleData)

# convert rows to 28x28 matrices and combine them into a single image stack
img = combine(lapply(1:nrow(df), function(i) Image(df[i, -1], c(28, 28))))

# set frame names to digit values
dimnames(img) = list(NULL, NULL, df[, 1])

# img contains an array of 3 grayscale 28x28 frames 
img
## Image 
## colorMode    : Grayscale 
##   storage.mode : double 
##   dim          : 28 28 3 
##   frames.total : 3 
##   frames.render: 3 
##
## imageData(object)[1:5,1:6,1]
##      [,1] [,2] [,3] [,4] [,5] [,6]
## [1,]    0    0    0    0    0    0
## [2,]    0    0    0    0    0    0
## [3,]    0    0    0    0    0    0
## [4,]    0    0    0    0    0    0
## [5,]    0    0    0    0    0    0

# display the first frame
display(getFrame(img, 1)/255, method = "raster", interpolate = FALSE)

img

请注意缩放1/255,这是因为EBImage使用[0:1]范围内的强度来绘制图像数据。

一旦您的数据格式正确,所有帧都可以使用函数rotate一次旋转。

# rotate of the whole image stack
imgr = rotate(img, angle = 5, output.dim = dim(img)[1:2])

display(getFrame(imgr, 1)/255, method = "raster", interpolate = FALSE)

enter image description here

要保留原始图片尺寸,我们需要设置output.dim,否则生成的图像会被展开,以便旋转的图像适合它。

在函数data.frame的帮助下,像素阵列可以平展回getFrames,函数返回图像帧列表。这些可以扩展到向量并逐行组合。原始数字名称前置为结果数据框的第一列。

dfr = as.data.frame(cbind(as.numeric(dimnames(imgr)[[3]]), 
                          do.call(rbind, lapply(getFrames(imgr), as.vector))))

使用在C中有效实现的通用affine transformation执行旋转。它使用双线性过滤,可以通过设置filter = "none"来关闭。根据我的microbenchmark测量结果,rotate函数比rotateImageM函数快约18倍(与原始rotateImage函数相比快了200倍)。