简体   繁体   English

如何通过R中的少量旋转位图

[英]How to Rotate a Bitmap by Small Amount in R

The Question: 问题:

This is in relation to the MNIST presentation of image data. 这与图像数据的MNIST表示有关。 I would like to do fine rotations of the images. 我想对图像进行精细旋转。 The set of all images is represented as a data.frame where each row (each image) is a 785 character vector; 所有图像的集合表示为data.frame,其中每一行(每个图像)是785个字符向量; the first character is the value of the digit; 第一个字符是数字的值; the remaining 784 are a flattened 28x28 pixel matrix. 其余784个像素是扁平的28x28像素矩阵。

I would like to efficiently rotate the 784 characters to generate a number of additional rotated images; 我想有效地旋转784个字符,以生成许多附加的旋转图像; ie I would like to engineer some new data! 即我想设计一些新数据!

I have already done this with the following code, but what I would like to do now is make it more efficient. 我已经使用以下代码完成了此操作,但是现在我想做的是使其更高效。 It currently takes hours to run on 42,000 images, using 6 threads on a 2.7GHz i7 machine. 在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)
}

Basically this starts with all pixels off (so sheering isn't a bother - I'm doing fine rotations, not 45 degree stuff), and in the two loops, any "on" pixels will be moved to their new locations for the rotated image. 基本上,这是从关闭所有像素开始的(这样就不用担心摇晃了-我做的是细微旋转,而不是45度的东西),并且在两个循环中,所有“开”像素都将移动到新位置进行旋转图片。 (This is, by the way, a simple way to do fine-angle image rotation in R, within the same "frame" of the original image.) (顺便说一下,这是在原始图像的同一“帧”内在R中进行细角度图像旋转的一种简单方法。)

I would like to eliminate the i and j loops with some kind of fancy one-liner transformation that runs faster. 我想通过运行更快的某种奇特的单线转换来消除i和j循环。 Any tips along those lines, or other novel ideas, would be much appreciated. 遵循这些思路的任何技巧或其他新颖的想法,将不胜感激。

A Solution: 一个解法:

One accepted answer (see @42- below), utilizes TsparseMatrix; 一个被接受的答案(见下面的@ 42-)利用了TsparseMatrix。 and has been slightly modified into a function here: 并已在此处稍微修改为一个函数:

####################################################################################################
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
}

For others curious about this same sort of question, you can probably see that the image dimensions (28x28) are arbitrary - specific to the examples I'm working on; 对于其他对此类问题感到好奇的人,您可能会发现图像尺寸(28x28)是任意的-特定于我正在研究的示例。 and these also govern the choice of "offsets" as either 14 or 13 in the solutions. 这些也决定了“偏移”的选择,在解决方案中是14还是13。 Therefore this should be easily extended to images of any size. 因此,应轻松将其扩展到任何尺寸的图像。

At the end of the day... 在一天结束时...

The proposed solutions all work, but for a large number of images were still a bit slow or memory inefficient. 提出的解决方案都可以使用,但是对于大量图像仍然有点慢或内存效率低下。 I ended up writing a simple app in VB.Net (which I know well, no other reason for that vs. anything else), and it processed 42,000 images coming in, generating 11 times that going out based on 10 rotations using the original rotateImage approach... aand it finished in under 6 minutes, including all the reading/writing to a non-SSD disk. 我最终在VB.Net中编写了一个简单的应用程序(我很清楚,没有其他理由与其他原因相比),它处理了42,000张rotateImage图像,使用原始的rotateImage根据10次旋转生成了11倍的rotateImage方法...并在6分钟内完成,包括所有对非SSD磁盘的读取/写入。 I'm still not sure why I couldn't get R to flip through everything with roughly that same efficiency. 我仍然不确定为什么我不能让R以大致相同的效率翻阅所有内容。

This demonstrates a strategy of working on the sparseMatrix representation: 这演示了处理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) )

在此处输入图片说明

 image( as.matrix(M) )

在此处输入图片说明

A different approach would be to use the functionality provided by the Bioconductor package EBImage , an image processing and analysis toolbox for R . 另一种方法是使用Bioconductor软件包EBImage提供的功能, EBImageR的图像处理和分析工具箱。 To install the package use: 要安装软件包,请使用:

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

You can then represent your image data as an Image object, and use the rotate function to transform it. 然后,您可以将图像数据表示为Image对象,并使用rotate函数对其进行转换。 This is illustrated by the following code. 下面的代码对此进行了说明。

If your original pixel data is stored in a data frame, you first need to convert it to an array-based format used by EBImage . 如果原始像素数据存储在数据框中,则首先需要将其转换为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

Note the scaling by 1/255, which is due to the fact that EBImage uses intensities in the [0:1] range to plot image data. 注意缩放比例为1/255,这是因为EBImage使用[0:1]范围内的强度来绘制图像数据。

Once your data is in the correct format, all frames can be rotated at once with the function rotate . 一旦您的数据采用正确的格式,就可以使用函数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)

在此处输入图片说明

To retain the original image dimensions we need to set output.dim , otherwise the resulting image would be expanded so that the rotated image fits into it. 要保留原始图像尺寸,我们需要设置output.dim ,否则将扩展生成的图像,以使旋转后的图像适合其中。

The pixel array can be flattend back to a data.frame with the help of the function getFrames which returns a list of image frames. 可以在函数getFrames的帮助下将像素数组展平为data.frame ,该函数返回图像帧列表。 These can be expanded to vectors and combined row-wise. 这些可以扩展为向量,并逐行组合。 The original digit names are prepended as the first column of the resulting data frame. 原始数字名称将作为结果数据帧的第一列。

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

The rotation is performed using the general affine transformation which is efficiently implemented in C. It uses bilinear filtering, which can be switched off by setting filter = "none" . 使用通用仿射变换执行旋转,该仿射变换在C中有效实现。它使用双线性滤波,可以通过将filter = "none"设置为关闭。 According to my microbenchmark measurements, the rotate function is around 18x faster than the rotateImageM function (and 200x faster compared to your original rotateImage function). 根据我的微基准测量, rotate功能比快18倍左右rotateImageM功能(和200X速度相比原来的rotateImage功能)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM