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.Информация о коде Приведенный выше код работает нормально, но абсолютно неэффективен. Из-за всех вычислений (особенно преобразования цвета и т. д.) Этот код занимает ДЛИННОЕ (слишком долгое) время, поэтому взгляните на этот снимок экрана:

Мой вопрос Я почти уверен, что есть ПУТЬ более простое решение для фильтрации определенного цвета (с заданным порогом #c6456f is similar to #C6476f, ...) вместо цикла по КАЖДОМУ пикселю для сравнения его цвета.
Некоторые заметки
Поэтому я не прошу вас публиковать ответы, содержащие предложения по использованию библиотека openCV. Я бы хотел разработать этот «алгоритм» исключительно на Swift.
Размер изображения, из которого был сделан снимок экрана, имел разрешение 500 * 800 пикселей.
Это все
Вы действительно дочитали до этого места? - поздравляю, однако - любая помощь, как ускорить мой код, была бы очень признательна! (Возможно, есть лучший способ получить цвет пикселя, а не зацикливаться на каждом пикселе) Заранее миллион :)
Потому что спектр RGB сильно отличается от нашего человеческого цветовой диапазон, в то время как HSL почти такой же! @mnistic
Вполне возможно, что преобразование RGB в HSV и расчет расстояния выполняются медленно. (Я полагаю, что RGBtoHSL - это функция, которую вы показали в другом вопросе.) Если вы должны провести сравнение в HSV, вы можете сначала выполнить грубую проверку в RGB и подробно исследовать расстояние HSV только тогда, когда цвета близки. Какова производительность, если вы соответствуете только точному цвету RGB?
Почти в 2 раза быстрее. Но на больших изображениях все равно тормозить! @MOehm
HSL совсем не "почти то же самое, что и наш человеческий цветовой диапазон" (что бы это ни значило). Наши глаза измеряют длину волны входящего света примерно так же, как это делает камера с RGB-подсветкой. Раннее зрение преобразует это в трехцветные значения (CIE Yxy пытается приблизить это, CIE Lab делает это лучше). HSL - это неудобное цветовое пространство, изобретенное для упрощения ввода цветов в пользовательский интерфейс. Это не подходит для анализа цвета. Сравните свои цвета в RGB, вы получите лучшие результаты и намного быстрее.
@CrisLuengo +1 за это ... люди уже несколько десятилетий ошибочно заменяют HSV / HSL цветами длины волны. Печально то, что молодые люди даже не знают, что они совсем не верны и используют это для вычислений, создавая беспорядок в физических данных ... см. Значения RGB видимого спектра. Вы даже не можете найти настоящее / правильное спектральное изображение в Интернете, что привело меня к этому коду и ответ я связал, так как мне это нужно для моделирования. То же самое и с черный цвет кузова / BV ... даже уравнения для них неверны ...
@Spektre: спасибо за ссылки, интересное чтение!





Первое, что нужно сделать - профилировать (измерить затраты времени на разные части вашей функции). Это часто показывает, что время тратится в неожиданном месте, и всегда подсказывает, куда направить усилия по оптимизации. Это не означает, что вы должны сосредоточиться на этом, наиболее трудоемком деле, но он покажет вам, на что вы тратите время. К сожалению, я не знаком со Swift, поэтому не могу рекомендовать какой-либо конкретный инструмент.
Что касается перебора всех пикселей - зависит от структуры изображения и ваших предположений о входных данных. Я вижу два случая, когда этого можно избежать:
Когда на вашем изображении построена некоторая оптимизированная структура данных (например, некоторая статистика в ее областях). Обычно это имеет смысл, когда вы обрабатываете одно и то же изображение одним и тем же (или похожим) алгоритмом с разными параметрами. Если вы обрабатываете каждое изображение только один раз, скорее всего, это вам не поможет.
Когда вы знаете, что зеленые пиксели всегда существуют в группе, поэтому не может быть изолированного единственного пикселя. В этом случае вы можете пропустить один или несколько пикселей и, обнаружив зеленый пиксель, проанализировать его окрестности.
Да, конечно, я знаю, что мне не нужно повторять мысли КАЖДОГО отдельного пикселя, но когда я пропускаю несколько пикселей, я действительно понятия не имею, как работать с "analyze its neighbourhood." afterwards.
@tempi, вы уже знаете, какая часть функции занимает больше всего времени? Вот с чего лучше всего начать ...
Я не пишу код на вашей платформе, но ...
Что ж, я предполагаю, что ваши замаскированные области (определенного цвета) непрерывны и достаточно велики ... это означает, что у вас есть группы пикселей вместе с достаточно большими областями (а не просто вещи толщиной в несколько пикселей). Исходя из этого предположения, вы можете создать карту плотности для своего цвета. Что я имею в виду, если минимальный размер детализации вашего конкретного цветового материала составляет 10 пикселей, вы можете проверять каждый 8-й пиксель на каждой оси, ускоряя начальное сканирование ~ 64 раза. А затем используйте полное сканирование только для областей, содержащих ваш цвет. Вот что вам нужно сделать:
определить свойства
Вам нужно установить шаг для каждой оси (сколько пикселей вы можете пропустить, не пропуская цветную зону). Назовем это dx, dy.
создать карту плотности
просто создайте 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;
увеличить области набора карты
теперь вы должны увеличить установленные области в map[][] до соседних ячеек, потому что # 2 может пропустить край вашей цветовой области.
обработать все заданные регионы
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 это работает?
Вместо того, чтобы преобразовывать каждый пиксель в HSL, почему бы вам просто не сделать целевой цвет в RGB. Также можно выложить
calculateHSLDistance?