【问题标题】:Most efficient way to crop image to circle (in R)?将图像裁剪为圆形(在 R 中)的最有效方法?
【发布时间】:2017-02-25 06:45:18
【问题描述】:

TL;DR:将矩形图像裁剪成圆形最有效的方法是什么?

说明/背景:

我正在编写 R 中的一些代码,它将 Spotify 艺术家图像显示为圆形,而不是默认的矩形/正方形。我找不到在 R 中裁剪图像的任何包或命令,尤其是圆形,所以我编写了自己的函数 circ,它读取 3 维(或 4 维)RGB(A) 数组并裁剪它们使用the parametric equation of a circle 来确定每个唯一 y 的 x 值。这是我的伪代码:

Given an RGB(A) array:
    Find the center of the image, radius = min(x coord, y coord)
    Pre-crop the image to a square of dimensions 2r x 2r
    For every unique y value:
        Determine the x coordinates on the circle
        Make pixels outside of the circle transparent
    Return the cropped image as an RGBA array

这个功能比我以前的功能有了很大的改进,它检查每个像素的位置,看它是在圆圈内还是在圆圈外,但我仍然觉得它可以进一步加速。

有没有办法我可以检查一半的 y 值而不是全部,然后镜像整个圆圈?我可以使用实际的裁剪功能吗?非常感谢任何和所有帮助!

编辑添加了一些复制粘贴运行代码(感谢@lukeA):

我原来的裁剪方法:

circ = function(a){
  # First part of the function finds the radius of the circle and crops the image accordingly
  xc = floor(dim(a[,,1])[2]/2)  # X coordinate of the center
  yc = floor(dim(a[,,1])[1]/2)  # Y coordinate of the center
  r = min(xc, yc) - 1  # Radius is the smaller of the two -1 to avoid reading nonexistent data
  ma = array(data = c(a[,,1][(yc-r):(yc+r),(xc-r):(xc+r)],  # Read in the cropped image
                      a[,,2][(yc-r):(yc+r),(xc-r):(xc+r)],  # Of dimensions 2r x 2r, centered
                      a[,,3][(yc-r):(yc+r),(xc-r):(xc+r)],  # Around (xc, yc)
                      rep(1,length(a[,,1][(yc-r):(yc+r),(xc-r):(xc+r)]))),  # Add fourth alpha layer
             dim = c(length((yc-r):(yc+r)),length((xc-r):(xc+r)),4))

  if(yc > xc) yc = xc else if(xc > yc) xc = yc  # Re-evaluate your center for the cropped image
  xmax = dim(ma[,,1])[2]; ymax = dim(ma[,,1])[1]  # Find maximum x and y values

  # Second part of the function traces circle by the parametric eqn. and makes outside pixels transparent
  for(y in 1:ymax){  # For every y in the cropped image
    theta = asin((y - yc) / r)  # y = yc + r * sin(theta) by parametric equation for a circle
    x = xc + r * cos(theta)  # Then we can find the exact x coordinate using the same formula
    x = which.min(abs(1:xmax - x))  # Find which x in array is closest to exact coordinate
    if(!x - xc == 0 && !xmax - x == 0){  # If you're not at the "corners" of the circle
      ma[,,4][y,c(1:(xmax-x), (x+1):xmax)] = 0  # Make pixels on either side of the circle trans.
    } else if(!xmax - x == 0) ma[,,4][y,] = 0  # This line makes tops/bottoms transparent
  }
  return(ma)
} 

library(jpeg)
a = readJPEG("http://1.bp.blogspot.com/-KYvXCEvK9T4/Uyv8xyDQnTI/AAAAAAAAHFY/swaAHLS-ql0/s1600/pink-smiley-face-balls-laughing-HD-image-for-faacebook-sharing.jpg")
par(bg = "grey"); plot(1:2, type="n")  # Color background to check transparency
rasterImage(circ(a),1,1,2,2)

修改版(感谢@dww):

dwwcirc = function(a){
  # First part of the function finds the radius of the circle and crops the image accordingly
  xc = floor(dim(a[,,1])[2]/2)  # X coordinate of the center
  yc = floor(dim(a[,,1])[1]/2)  # Y coordinate of the center
  r = min(xc, yc) - 1  # Radius is the smaller of the two -1 to avoid reading nonexistent data
  ma = array(data = c(a[,,1][(yc-r):(yc+r),(xc-r):(xc+r)],  # Read in the cropped image
                      a[,,2][(yc-r):(yc+r),(xc-r):(xc+r)],  # Of dimensions 2r x 2r, centered
                      a[,,3][(yc-r):(yc+r),(xc-r):(xc+r)],  # Around (xc, yc)
                      rep(1,length(a[,,1][(yc-r):(yc+r),(xc-r):(xc+r)]))),  # Add fourth alpha layer
             dim = c(length((yc-r):(yc+r)),length((xc-r):(xc+r)),4))

  if(yc > xc) yc = xc else if(xc > yc) xc = yc  # Re-evaluate your center for the cropped image
  xmax = dim(ma[,,1])[2]; ymax = dim(ma[,,1])[1]  # Find maximum x and y values

  x = rep(1:xmax, ymax)  # Vector containing all x values
  y = rep(1:ymax, each=xmax)  # Value containing all y values
  r2 = r^2
  ma[,,4][which(( (x-xc)^2 + (y-yc)^2 ) > r2)] = 0
  return(ma)
} 

library(jpeg)
a = readJPEG("http://1.bp.blogspot.com/-KYvXCEvK9T4/Uyv8xyDQnTI/AAAAAAAAHFY/swaAHLS-ql0/s1600/pink-smiley-face-balls-laughing-HD-image-for-faacebook-sharing.jpg")
par(bg = "grey"); plot(1:2, type="n")  # Color background to check transparency
rasterImage(dwwcirc(a),1,1,2,2)

使用 magick 和 plotrix 的版本(感谢 @lukeA 和 @hrbrmstr):

library(plotrix)
jpeg(tf <- tempfile(fileext = "jpeg"), 1000, 1000)
par(mar = rep(0,4), yaxs="i", xaxs = "i")
plot(0, type = "n", ylim = c(0, 1), xlim = c(0,1), axes=F, xlab=NA, ylab=NA)
draw.circle(.5,.5,.5,col="black")
dev.off()

library(magick)
img = image_read("http://1.bp.blogspot.com/-KYvXCEvK9T4/Uyv8xyDQnTI/AAAAAAAAHFY/swaAHLS-ql0/s1600/pink-smiley-face-balls-laughing-HD-image-for-faacebook-sharing.jpg")
mask = image_read(tf)
radius = min(c(image_info(img)$width, image_info(img)$height))
mask = image_scale(mask, as.character(radius))

par(bg = "grey"); plot(1:2, type="n")
rasterImage(as.raster(image_composite(image = mask, composite_image = img, operator = "plus")),1,1,2,2)

【问题讨论】:

  • 您可以使用 imager::imsub 进行裁剪,但它只需要矩形参数。你将不得不坚持你正在做的事情,但这确实会减少你的启发式步骤。
  • 你不应该使用真正的图像处理工具,而不是像这样尝试破解 R 吗?
  • [我只读过 tl;dr] 有一些带有网格图形的示例可以制作光栅蒙版
  • 您可以使用新的magick 包进行非常强大的图像转换。
  • @HongOoi 我曾想过这样做,但由于我将拉出 1,000 张以上的图像,这些图像不能保证每次都相同,因此我决定将其直接编码到我的脚本中会更容易.你知道是否有办法调用 Photoshop 或 gimp 来裁剪图像?

标签: r crop


【解决方案1】:

如果您使用(x-xc)^2 +(y-yc)^2 &gt; r^2 用于圆外的点这一事实对数组执行矢量化子集分配操作(而不是循环),则可以提高circ 函数的性能。

为此,请将函数的第二部分替换为

  # Second part of the function traces circle by...
  x = rep(1:xmax, ymax)
  y = rep(1:ymax, each=xmax)
  r2 = r^2
  ma[,,4][which(( (x-xc)^2 + (y-yc)^2 ) > r2)] <- 0
  return(ma)

【讨论】:

  • 哇!我尝试了这个并在decent-sized jpeg 上检查了运行时。它只需要不到十分之一的时间(根据proc.time(),从 16.887 下降到 1.232 秒),这太疯狂了。我会立即实施,谢谢!
  • 太好了,很高兴它有帮助。只是一个提示。 x & y 可能 的定义需要交换,具体取决于您的数组是按列顺序还是行顺序定义的。如果它们是错误的,这是一个简单的修复 - (如果圆从非方形图像的中心偏移,您将知道是否需要调整)。如果发生这种情况,请告诉我,我会相应地解决答案
  • 我没有注意到任何比原来更偏离中心的东西(感谢定义半径时的 -1),但看起来你调用数组 a 中的特定单元格,维度 d a[,,d][y,x] ,所以我认为你是金子。
【解决方案2】:

我不知道“效率”,但我不会在这里重新发明轮子。就像@hrbrmstr 在 cmets 中建议的那样,您可能想尝试magick,它为您提供了您可能需要的所有灵活性:

png(tf <- tempfile(fileext = ".png"), 1000, 1000)
par(mar = rep(0,4), yaxs="i", xaxs="i")
plot(0, type = "n", ylim = c(0,1), xlim=c(0,1), axes=F, xlab=NA, ylab=NA)
plotrix::draw.circle(.5,0.5,.5, col="black")
dev.off()

library(magick)
fn <- "https://www.gravatar.com/avatar/f57aba01c52e5c67696817eb87df84f2?s=328&d=identicon&r=PG&f=1"
img <- image_read(fn)
mask <- image_read(tf)
mask <- image_scale(mask, as.character(image_info(img)$width))

现在

img

mask

image_composite(mask, img, "plus") 

image_composite(mask, img, "minus") 

其他一些composite operators:

# https://www.imagemagick.org/Magick++/Enumerations.html#CompositeOperator
ops <- c("over", "in", "out", "atop", "xor", "plus", "minus", "add",  "difference", "multiply")
for (op in ops) {
  print(image_composite(img, mask, op))
  print(op)
  readline()
}

【讨论】:

  • 我现在试试这个。在我尝试自己弄清楚时,我确实有几个问题要问你。
  • 糟糕,我是评论新手——我没有意识到按 Enter 会发帖。首先,spotify 将他们的艺术家图片保存为 jpeg,而不是 PNG,但我认为这应该可以正常工作。另一个问题是“减号”是否会删除(或使您不可见)您遮盖的区域?还是它保留它,就像颜色的反面一样?谢谢
  • #1 的工作原理相同,是的。 #2“减号”是“合成图像 - 图像,溢出裁剪为零。哑光通道被忽略(设置为 255,完全覆盖)。”。所以,黑色面具意味着它是完全可见的。 (灰色使它变暗。白色是黑色。)
  • 好的,所以我对composite_image 和各种运算符(感谢链接)有了一些了解,虽然这似乎是我应该做的,而不是搞乱数组,圆圈周围的区域肯定是不透明的。运行par(bg = "grey"); plot(1:2) 后跟rasterImage(as.raster(image_composite(mask, img, "plus")),1,1,2,2) 表明背景被遮挡。由于圆圈周围的区域是透明的而不是白色/黑色是相当重要的,有没有办法解决这个问题?也许使用“in”或“copyopacity”?
  • 好的,我输入了我拥有的三个变体。在运行所有三个之后,@dww 的建议具有最低的运行时间(1.420 秒),然后是您的变体(7.027 秒)和我原来的(16.749 秒)。此外,image_composite 版本删除了使用其他两个版本可见的灰色背景,我认为这意味着图像并没有真正裁剪为圆形,而是将圆形裁剪为白色。也许我实现不正确?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-10-08
  • 2016-05-01
  • 2021-10-08
  • 1970-01-01
  • 2013-03-22
  • 1970-01-01
相关资源
最近更新 更多