【问题标题】:custom image filter自定义图像过滤器
【发布时间】:2018-08-26 13:46:36
【问题描述】:

1.简介:

所以我想为uiimages开发一种特殊的过滤方法——我的想法是把一张图片的所有颜色都变成黑色,除了某种颜色,应该保持它们的外观。

图片总是很漂亮,所以看看这张图片来了解我想要实现的目标:

2.说明:

我想应用能够在图像中找到特定颜色的过滤器(算法)。该算法必须能够将所有与参考颜色不匹配的颜色替换为例如“黑色”。

我开发了一个简单的代码,它能够替换任何图像中的特定颜色(带阈值的颜色范围)。 但是这个解决方案似乎根本不是一种快速有效的方法!


func colorFilter(image: UIImage, findcolor: String, threshold: Int) -> UIImage {
    let img: CGImage = image.cgImage!
    let context = CGContext(data: nil, width: img.width, height: img.height, bitsPerComponent: 8, bytesPerRow: 4 * img.width, space: CGColorSpaceCreateDeviceRGB(), bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue)!
    context.draw(img, in: CGRect(x: 0, y: 0, width: img.width, height: img.height))
    let binaryData = context.data!.assumingMemoryBound(to: UInt8.self),
        referenceColor = HEXtoHSL(findcolor) // [h, s, l] integer array
    for i in 0..<img.height {
        for j in 0..<img.width {
            let pixel = 4 * (i * img.width + j)
            let pixelColor = RGBtoHSL([Int(binaryData[pixel]), Int(binaryData[pixel+1]), Int(binaryData[pixel+2])])  // [h, s, l] integer array
            let distance = calculateHSLDistance(pixelColor, referenceColor) // value between 0 and 100
            if (distance > threshold) {
                let setValue: UInt8 = 255
                binaryData[pixel] = setValue; binaryData[pixel+1] = setValue; binaryData[pixel+2] = setValue; binaryData[pixel+3] = 255
            }
        }
    }
    let outputImg = context.makeImage()!
    return UIImage(cgImage: outputImg, scale: image.scale, orientation: image.imageOrientation)
}


3.代码信息 上面的代码运行良好,但绝对无效。由于所有的计算(尤其是颜色转换等),这段代码需要很长时间(太长),所以看看这个截图:


  1. 我的问题我很确定有一种更简单的过滤特定颜色(具有给定阈值 #c6456f is similar to #C6476f, ...)的解决方案,而不是循环遍历每个像素来比较它的颜色。

    • 所以我想的是过滤器(CIFilter 方法)之类的东西,作为顶部代码的替代方法。
  2. 一些注意事项

    • 因此,我不要求您发布任何包含使用 openCV 库的建议的回复。我想专门用 Swift 开发这个“算法”。

    • 随着时间的推移,截取屏幕截图的图像大小的分辨率为 500 * 800 像素

  3. 仅此而已

你真的读到这里了吗? - 恭喜 - 任何帮助如何加速我的代码将非常感谢(也许有更好的方法来获取像素颜色而不是循环遍历每个像素)提前一百万 :)

【问题讨论】:

  • 与其将每个像素都转换为 HSL,不如直接将目标颜色设为 RGB。另外,你能发帖calculateHSLDistance吗?
  • 因为 RGB 光谱与我们人类的颜色范围大不相同,而 HSL 几乎相同! @mnistic
  • 很可能是您的 RGB 到 HSV 转换和距离计算很慢。 (我猜RGBtoHSL 是您在另一个问题中显示的功能。)如果您必须在 HSV 中进行比较,您可以先在 RGB 中进行粗略检查,并且仅在颜色接近时才详细调查 HSV 距离。如果只匹配精确的 RGB 颜色,性能如何?
  • HSL 根本不是“与我们人类的颜色范围几乎相同”(无论这意味着什么)。我们的眼睛测量入射光波长的方式与 RGB 相机大致相同。早期视觉将其转换为三色值(CIE Yxy 试图近似此值,CIE 实验室做得更好)。 HSL 是一种尴尬的色彩空间,旨在让用户更容易在 UI 中输入颜色。它不适合颜色分析。在 RGB 中比较您的颜色,您将获得更好的结果,而且速度更快。
  • @Spektre:感谢这些链接,有趣的阅读!

标签: ios swift image algorithm image-processing


【解决方案1】:

我不在你的平台上编码,但是...

好吧,我假设您的蒙版区域(具有特定颜色)是连续的并且足够大……这意味着您将像素组和足够大的区域(不仅仅是几个像素厚的东西)放在一起。有了这个假设,您可以为您的颜色创建密度图。我的意思是,如果您的特定颜色材料的最小细节尺寸为 10 像素,那么您可以检查每个轴上的每 8 个像素,从而加快初始扫描约 64 倍。然后仅对包含您的颜色的区域使用完整扫描。这是你必须做的:

  1. 确定属性

    您需要为每个轴设置步长(可以跳过多少像素而不会错过彩色区域)。让我们称之为 dx,dy。

  2. 创建密度图

    如果区域的中心像素设置为您的特定颜色,则只需创建将保存信息的 2D 数组。因此,如果您的图像具有xs,ys 分辨率,那么您的地图将是:

    int mx=xs/dx;
    int my=ys/dy;
    int map[mx][my],x,y,xx,yy;
    
    for (yy=0,y=dy>>1;y<ys;y+=dy,yy++)
     for (xx=0,x=dx>>1;x<xs;x+=dx,xx++)
      map[xx][yy]=compare(pixel(x,y) , specific_color)<threshold;
    
  3. 放大地图集区域

    现在您应该将map[][] 中的设置区域扩大到相邻的单元格,因为#2 可能会错过您的颜色区域的边缘。

  4. 处理所有设置的区域

    for (yy=0;yy<my;yy++)
     for (xx=0;xx<mx;xx++)
      if (map[xx][yy])
       for (y=yy*dy,y<(yy+1)*dy;y++)
        for (x=xx*dx,x<(xx+1)*dx;x++)
         if (compare(pixel(x,y) , specific_color)>=threshold) pixel(x,y)=0x00000000;
    

如果您想加快这一速度,甚至比检测位于边缘的集合map[][] 单元格(至少有一个零邻居),您可以区分这些单元格,例如:

0 - no specific color is present
1 - inside of color area
2 - edge of color area

这可以通过简单地在O(mx*my) 中完成。之后,您只需要检查边缘区域的颜色:

for (yy=0;yy<my;yy++)
 for (xx=0;xx<mx;xx++)
  if (map[xx][yy]==2)
   {
   for (y=yy*dy,y<(yy+1)*dy;y++)
    for (x=xx*dx,x<(xx+1)*dx;x++)
     if (compare(pixel(x,y) , specific_color)>=threshold) pixel(x,y)=0x00000000;
   } else if (map[xx][yy]==0)
   {
   for (y=yy*dy,y<(yy+1)*dy;y++)
    for (x=xx*dx,x<(xx+1)*dx;x++)
     pixel(x,y)=0x00000000;
   }

这应该会更快。如果您的图像分辨率xs,ys 不是区域大小mx,my 的倍数,您应该通过零填充或图像缺失部分的特殊循环来处理图像的外边缘...

顺便说一句,读取和设置整个图像需要多长时间?

for (y=0;y<ys;y++)
 for (x=0;x<xs;x++)
  pixel(x,y)=pixel(x,y)^0x00FFFFFF;

如果仅此一项速度比这意味着您的像素访问速度太慢,您应该为此使用不同的 api。这在 Windows GDI 平台上是很常见的错误,因为人们通常使用Pixels[][],它比爬行蜗牛慢。还有其他方法,例如 bitlocking/blitting、ScanLine 等,因此在这种情况下,您需要在您的平台上快速寻找一些东西。如果你连这些东西都不能加快速度,那么你就不能做其他任何事情......顺便说一句,这是在什么硬件上运行的?

【讨论】:

    【解决方案2】:

    要做的第一件事 - 配置文件(测量功能不同部分的时间消耗)。它通常表明时间花在了一些意想不到的地方,并且总是建议您在哪里进行优化工作。但这并不意味着您必须专注于最耗时的事情,但它会告诉您时间花在了哪里。不幸的是,我不熟悉 Swift,所以不能推荐任何特定的工具。

    关于遍历所有像素 - 取决于图像结构和您对输入数据的假设。我看到两种情况可以避免这种情况:

    1. 当在您的图像上构建了一些优化的数据结构时(例如,其区域中的一些统计信息)。当您使用具有不同参数的相同(或相似)算法处理相同图像时,这通常是有意义的。如果您只处理每张图片一次,可能对您没有帮助。

    2. 当您知道绿色像素始终存在于一个组中时,因此不可能有孤立的单个像素。在这种情况下,您可以跳过一个或多个像素,当您找到一个绿色像素时,分析它的邻域。

    【讨论】:

    • 是的,我当然知道我不需要对每个像素进行迭代,但是当我跳过一些像素时,我真的不知道如何"analyze its neighbourhood." afterwards
    • @tempi,您是否已经知道函数的哪个部分消耗了大部分时间?这是最好的开始......
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-09
    • 1970-01-01
    • 2013-08-17
    相关资源
    最近更新 更多