Для моего проекта я сначала загружаю изображение из файла и помещаю каждый пиксель в 2D-массив pixels[,]
. Затем я хочу проверить каждый пиксель и разделить их на «ячейки» в зависимости от того, как они окрашены, а затем отсортировать каждую ячейку. Итак, у меня есть объект Bin
, который инкапсулирует List<Pixel>
, и List<Bin>
, содержащий (относительно небольшое) количество бинов.
Моя проблема в том, что когда я пытаюсь заполнить эти ячейки очень большими изображениями (например, 1920x1200 = 2,3 миллиона пикселей), используемый мной алгоритм работает медленнее, чем хотелось бы, и я проследил проблему до некоторого C#. специфические для языка функции, которые, кажется, занимают больше времени, чем я ожидал. Я хотел бы получить несколько советов о том, как лучше использовать C# для устранения этих узких мест.
После загрузки изображения я вызываю функцию fillBinsFromSource, которая берет перечисляемый список пикселей, находит, к какой ячейке они принадлежат, и помещает их туда:
public void fillBinsFromSource(IEnumerable<Pixel> source)
{
Stopwatch s = new Stopwatch();
foreach (Pixel p in source)
{
s.Start();
// algorithm removed for brevity
// involves a binary search of all Bins and a List.Add call
s.Stop();
}
}
Для больших изображений ожидается, что мой алгоритм займет какое-то время, но когда я помещаю Секундомер вне вызова функции, оказывается, что это занимает примерно в два раза больше времени, начисленного на s
, а это означает, что выполнение перечисления с использованием foreach
является занимает половину времени этой функции (около 800 мс из 1,6 секунды для изображения 1920x1200).
Причина, по которой мне нужно передать перечисляемый список, заключается в том, что иногда пользователи добавляют только небольшую область изображения, а не все изображение. Отнимающий много времени вызов передает несколько итераторов, сначала из списка изображений, затем из каждого изображения в списке, например так (упрощенно):
class ImageList : IEnumerable<Pixel>
{
private List<Image> imageList;
public IEnumerator<Pixel> GetEnumerator()
{
foreach (Image i in imageList)
foreach (Pixel p in i)
yield return p;
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
class Image : IEnumerable<Pixel>
{
private Pixel[,] pixels; // all pixels in the image
private List<Pixel> selectedPixels;// all pixels in the user's selection
public IEnumerator<Pixel> GetEnumerator()
{
if (selectedPixels == null)
for (int i = 0; i < image.Width; i++)
for (int j = 0; j < image.Height; j++)
yield return pixels[i, j];
else
for (int i = 0; i < selectedPixels.Count; i++)
yield return selectedPixels[i];
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
Затем, наконец, я называю это
ImageList list; // pretend it contains only 1 image, and it's large
fillBinsFromSource(list);
Вопрос 1) Из-за необходимости перечисления как 2D-массива пикселей, так и выбранной области, в зависимости от того, что выбрал пользователь, перечисление просто очень медленное. Как я могу ускорить это?
Затем, заполнив все эти корзины Pixel
объектами, я их сортирую. Я вызываю List<Pixel>.Sort()
и полагаюсь на IComparable
, вот так:
ImageList list; // pretend it contains only 1 image, and it's large
fillBinsFromSource(list);
foreach(Bin b in allBins)
b.Sort(); // calls List<Pixel>.Sort()
class Pixel : IComparable
{
// store both HSV and RGB
float h, s, v;
byte r, g, b;
// we sort by HSV's value property
public int CompareTo(object obj)
{
// this is much faster than calling CompareTo on a float
Pixel rhs = obj as Pixel;
if (v < rhs.v)
return -1;
else if (v > rhs.v)
return 1;
return 0;
}
Вопрос 2) Предположим, что allBins
имеет 7 элементов; сортировка 7 отдельных списков, содержащих в общей сложности 2,3 миллиона Pixel
s, занимает около 2 секунд. Сортировка одного списка из 2,3 миллиона случайных int
s занимает менее 200 миллисекунд. Я могу оценить, что есть некоторый уровень ускорения при использовании примитивных типов, но действительно ли использование IComparable
более чем в 10 раз медленнее? Есть ли здесь ускорение?
Прошу прощения за длинный вопрос, если у кого-то есть какие-либо советы для меня, я был бы признателен!
fillBinsFromSource
я бы просто выполнил базовый цикл в классеImage
и использовал обратный вызов. Это кажется особенно (и излишне) грязным с точки зрения дизайна, но я бы вообще избегал использования каких-либо итераторов. Я хотел бы не запутать свой дизайн, если это возможно. - person XenoScholar   schedule 01.12.2012Bitmap
до этого проекта и ошибочно предположил, чтоGetPixel
реализован прилично. До недавнего внедрения исправления, включающегоLockBits
, простая загрузка изображения в исходный массив пикселей занимала около 5 секунд. К счастью, это было исправлено, и загрузка изображения не связана с приведенным выше кодом. - person XenoScholar   schedule 01.12.2012