Настраиваемый фильтр изображений

1.Вступление:

So I want to develop a special filter method for uiimages - my idea is to change from one picture all the colors to black except a certain color, which should keep their appearance.

Изображения всегда красивы, поэтому посмотрите на это изображение, чтобы понять, чего я хотел бы достичь:

Настраиваемый фильтр изображений

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?

mnistic 17.03.2018 17:17

Потому что спектр RGB сильно отличается от нашего человеческого цветовой диапазон, в то время как HSL почти такой же! @mnistic

user9507127 17.03.2018 17:36

Вполне возможно, что преобразование RGB в HSV и расчет расстояния выполняются медленно. (Я полагаю, что RGBtoHSL - это функция, которую вы показали в другом вопросе.) Если вы должны провести сравнение в HSV, вы можете сначала выполнить грубую проверку в RGB и подробно исследовать расстояние HSV только тогда, когда цвета близки. Какова производительность, если вы соответствуете только точному цвету RGB?

M Oehm 17.03.2018 17:52

Почти в 2 раза быстрее. Но на больших изображениях все равно тормозить! @MOehm

user9507127 17.03.2018 18:22

HSL совсем не "почти то же самое, что и наш человеческий цветовой диапазон" (что бы это ни значило). Наши глаза измеряют длину волны входящего света примерно так же, как это делает камера с RGB-подсветкой. Раннее зрение преобразует это в трехцветные значения (CIE Yxy пытается приблизить это, CIE Lab делает это лучше). HSL - это неудобное цветовое пространство, изобретенное для упрощения ввода цветов в пользовательский интерфейс. Это не подходит для анализа цвета. Сравните свои цвета в RGB, вы получите лучшие результаты и намного быстрее.

Cris Luengo 17.03.2018 23:01

@CrisLuengo +1 за это ... люди уже несколько десятилетий ошибочно заменяют HSV / HSL цветами длины волны. Печально то, что молодые люди даже не знают, что они совсем не верны и используют это для вычислений, создавая беспорядок в физических данных ... см. Значения RGB видимого спектра. Вы даже не можете найти настоящее / правильное спектральное изображение в Интернете, что привело меня к этому коду и ответ я связал, так как мне это нужно для моделирования. То же самое и с черный цвет кузова / BV ... даже уравнения для них неверны ...

Spektre 18.03.2018 11:13

@Spektre: спасибо за ссылки, интересное чтение!

Cris Luengo 18.03.2018 14:52
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
7
187
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Первое, что нужно сделать - профилировать (измерить затраты времени на разные части вашей функции). Это часто показывает, что время тратится в неожиданном месте, и всегда подсказывает, куда направить усилия по оптимизации. Это не означает, что вы должны сосредоточиться на этом, наиболее трудоемком деле, но он покажет вам, на что вы тратите время. К сожалению, я не знаком со Swift, поэтому не могу рекомендовать какой-либо конкретный инструмент.

Что касается перебора всех пикселей - зависит от структуры изображения и ваших предположений о входных данных. Я вижу два случая, когда этого можно избежать:

  1. Когда на вашем изображении построена некоторая оптимизированная структура данных (например, некоторая статистика в ее областях). Обычно это имеет смысл, когда вы обрабатываете одно и то же изображение одним и тем же (или похожим) алгоритмом с разными параметрами. Если вы обрабатываете каждое изображение только один раз, скорее всего, это вам не поможет.

  2. Когда вы знаете, что зеленые пиксели всегда существуют в группе, поэтому не может быть изолированного единственного пикселя. В этом случае вы можете пропустить один или несколько пикселей и, обнаружив зеленый пиксель, проанализировать его окрестности.

Да, конечно, я знаю, что мне не нужно повторять мысли КАЖДОГО отдельного пикселя, но когда я пропускаю несколько пикселей, я действительно понятия не имею, как работать с "analyze its neighbourhood." afterwards.

user9507127 17.03.2018 17:38

@tempi, вы уже знаете, какая часть функции занимает больше всего времени? Вот с чего лучше всего начать ...

maxim1000 17.03.2018 18:59
Ответ принят как подходящий

Я не пишу код на вашей платформе, но ...

Что ж, я предполагаю, что ваши замаскированные области (определенного цвета) непрерывны и достаточно велики ... это означает, что у вас есть группы пикселей вместе с достаточно большими областями (а не просто вещи толщиной в несколько пикселей). Исходя из этого предположения, вы можете создать карту плотности для своего цвета. Что я имею в виду, если минимальный размер детализации вашего конкретного цветового материала составляет 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[][], который работает медленнее, чем ползучая улитка. есть и другие способы, такие как битовая блокировка / блиттинг, ScanLine и т. д., поэтому в таком случае вам нужно быстро найти что-то на своей платформе. Если вы не можете ускорить даже этот материал, вы не можете делать ничего другого ... кстати, на каком HW это работает?

Другие вопросы по теме