Вы действительно передаете число с плавающей запятой в процентах isMatching
?
Я посмотрел ваш код для isMatching на GitHub и, ну, да. Вы портировали это с Java, верно? В C# используется bool
, а не Boolean
, и хотя я не уверен в этом, мне не нравится внешний вид кода, который так много упаковывает и распаковывает. Кроме того, вы выполняете массу умножения и сравнения с плавающей запятой, когда вам это не нужно:
public static bool IsMatching(Color a, Color b, int percent)
{
//this method is used to identify whether two pixels,
//of color a and b match, as in they can be considered
//a solid color based on the acceptance value (percent)
int thresh = (int)(percent * 255);
return Math.Abs(a.R - b.R) < thresh &&
Math.Abs(a.G - b.G) < thresh &&
Math.Abs(a.B - b.B) < thresh;
}
Это сократит объем работы, которую вы выполняете на пиксель. Мне все еще это не нравится, потому что я стараюсь избегать вызовов методов в середине цикла для каждого пикселя, особенно в цикле 8x для каждого пикселя. Я сделал метод статическим, чтобы сократить количество передаваемых экземпляров, которые не используются. Одни только эти изменения, вероятно, удвоят вашу производительность, так как мы делаем только 1 умножение, без упаковки, и теперь используем присущее && короткое замыкание, чтобы сократить работу.
Если бы я делал это, я бы, скорее всего, сделал что-то вроде этого:
// assert: bitmap.Height > 2 && bitmap.Width > 2
BitmapData data = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height),
ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
int scaledPercent = percent * 255;
unsafe {
byte* prevLine = (byte*)data.Scan0;
byte* currLine = prevLine + data.Stride;
byte* nextLine = currLine + data.Stride;
for (int y=1; y < bitmap.Height - 1; y++) {
byte* pp = prevLine + 3;
byte* cp = currLine + 3;
byte* np = nextLine + 3;
for (int x = 1; x < bitmap.Width - 1; x++) {
if (IsEdgeOptimized(pp, cp, np, scaledPercent))
{
// do what you need to do
}
pp += 3; cp += 3; np += 3;
}
prevLine = currLine;
currLine = nextLine;
nextLine += data.Stride;
}
}
private unsafe static bool IsEdgeOptimized(byte* pp, byte* cp, byte* np, int scaledPecent)
{
return IsMatching(cp, pp - 3, scaledPercent) &&
IsMatching(cp, pp, scaledPercent) &&
IsMatching(cp, pp + 3, scaledPercent) &&
IsMatching(cp, cp - 3, scaledPercent) &&
IsMatching(cp, cp + 3, scaledPercent) &&
IsMatching(cp, np - 3, scaledPercent) &&
IsMatching(cp, np, scaledPercent) &&
IsMatching(cp, np + 3, scaledPercent);
}
private unsafe static bool IsMatching(byte* p1, byte* p2, int thresh)
{
return Math.Abs(p1++ - p2++) < thresh &&
Math.Abs(p1++ - p2++) < thresh &&
Math.Abs(p1 - p2) < thresh;
}
Что теперь делает все виды ужасных искажений указателя, чтобы сократить доступ к массиву и так далее. Если вся эта работа с указателями вызывает у вас дискомфорт, вы можете выделить массивы байтов для prevLine, currLine и nextLine и выполнить Marshal.Copy для каждой строки по мере продвижения.
Алгоритм таков: начните с одного пикселя сверху и слева и перебирайте каждый пиксель изображения, кроме внешнего края (без граничных условий! Ура!). Я сохраняю указатели на начало каждой строки, prevLine, currLine и nextLine. Затем, когда я запускаю цикл x, я составляю pp, cp, np, которые являются предыдущим пикселем, текущим пикселем и следующим пикселем. текущий пиксель действительно тот, о котором мы заботимся. pp — пиксель прямо над ним, np — прямо под ним. Я передаю их в IsEdgeOptimized, который просматривает cp, вызывая IsMatching для каждого.
Теперь все это предполагает 24 бита на пиксель. Если вы смотрите на 32 бита на пиксель, все эти магические 3 должны быть 4, но в остальном код не меняется. Вы можете параметризовать количество байтов на пиксель, если хотите, чтобы он мог обрабатывать и то, и другое.
К вашему сведению, каналы в пикселях обычно имеют вид b, g, r, (a).
Цвета хранятся в виде байтов в памяти. Ваше фактическое растровое изображение, если это 24-битное изображение, хранится в виде блока байтов. Строки развертки имеют ширину data.Stride
байт, что как минимум равно 3 * количеству пикселей в строке (может быть больше, поскольку строки развертки часто дополняются).
Когда я объявляю переменную типа byte *
в C#, я делаю несколько вещей. Во-первых, я говорю, что эта переменная содержит адрес расположения байта в памяти. Во-вторых, я говорю, что собираюсь нарушить все меры безопасности в .NET, потому что теперь я могу читать и записывать любой байт в памяти, что может быть опасно.
Итак, когда у меня есть что-то вроде:
Math.Abs(*p1++ - *p2++) < thresh
Что он говорит (и это будет долго):
- Возьмите байт, на который указывает p1, и удерживайте его.
- Добавьте 1 к p1 (это ++ - указатель указывает на следующий байт)
- Возьмите байт, на который указывает p2, и удерживайте его.
- Добавить 1 к p2
- Вычесть шаг 3 из шага 1
- Передайте это
Math.Abs
.
Причина этого заключается в том, что исторически чтение содержимого байта и продвижение вперед было очень распространенной операцией, которую многие процессоры встраивали в единую операцию из пары инструкций, которые конвейерно выполнялись в одном цикле или около того.
Когда мы вводим IsMatching
, p1
указывает на пиксель 1, p2
указывает на пиксель 2 и в памяти они располагаются так:
p1 : B
p1 + 1: G
p1 + 2: R
p2 : B
p2 + 1: G
p2 + 2: R
Таким образом, IsMatching
просто вычисляет абсолютную разницу при обходе памяти.
Ваш последующий вопрос говорит мне, что вы действительно не понимаете указатели. Это нормально - вы, вероятно, можете их выучить. Честно говоря, концепции на самом деле не так уж сложны, но проблема с ними заключается в том, что без большого опыта вы, скорее всего, выстрелите себе в ногу, и, возможно, вам следует подумать только об использовании инструмента профилирования вашего кода и охлаждения. вниз по худшим горячим точкам и назвать это хорошим.
Например, вы заметите, что я смотрю с первой строки на предпоследнюю строку и с первого столбца на предпоследний столбец. Это делается намеренно, чтобы избежать обработки случая «Я не могу прочитать выше 0-й строки», что устраняет большой класс потенциальных ошибок, которые могут включать чтение за пределы допустимого блока памяти, что может быть безопасным во многих условиях выполнения.
person
plinth
schedule
18.04.2013
LockBits
дает вам массив байтов, к которому вы можете получить прямой доступ. Маловероятно, что вы найдете более быстрый способ доступа к данным. Если вы хотите, чтобы кто-нибудь посмотрел на ваш алгоритм, либо опубликуйте соответствующие части здесь, либо, по крайней мере, сообщите нам, какой файл в этом проекте GitHub вы хотите, чтобы мы просмотрели. Не заставляйте нас просматривать десятки ненужных файлов, чтобы найти то, о чем вы спрашиваете. - person Jim Mischel   schedule 18.04.2013