Впервые я познакомился с цифровой обработкой изображений в старшей школе. В то время PHOTOSHOP был еще версии 4.0. Возможно, из-за моих предубеждений я до сих пор не заинтересован в изучении 3DMAX и тому подобного. Переход от 2D к 3D, вероятно, не к чему. делайте со мной, и я не могу оставить эту площадь с кубической высокой зарплатой.... Хаха.
Когда я учился в колледже, я вместе с одноклассниками написал несколько программ обработки изображений. В то время программирование было еще очень случайным, и все, о чем я думал, это то, как его реализовать. Теперь кажется, что настоящая технология — это способность понимать общее. ситуации, а не волшебной вспышки вдохновения. Несколько дней назад я познакомился с некоторыми зарубежными программами обработки изображений. Вот краткое изложение: я не буду специально изучать обработку изображений в будущем.
Бывший одноклассник как-то сказал мне, что в .net нет указателей. Сейчас многие обучающие курсы говорят то же самое. На самом деле это заблуждение. Просто фреймворк не рекомендует использовать указатели, особенно в межпроцессных операциях, таких как веб-сервис и удаленное взаимодействие, указатели небезопасны. Но каждый, кто использовал TC, должен быть глубоко впечатлен эффективностью выполнения указателей. При необходимости пакетного расчета крупномасштабных данных указатели являются единственным выбором. Поэтому .net ловко сохраняет указатель и помещает его в набор небезопасных методов. Разумное использование указателей значительно повысит эффективность выполнения. Я проводил эксперименты по выполнению поточечных операций с изображением 640*480. Операции без указателей занимают несколько минут, тогда как операции с указателями выполняются практически мгновенно. Так что не бойтесь использовать указатели.
Второе - математика. Советую всем разобраться в ней, прежде чем писать программу. Урок математики - это не шутка... Если не понимаешь, придется лежать в постели и думать об этом снова и снова, я всегда чувствую. что математика может предотвратить болезнь Альцгеймера.
Подробнее о структуре программы:
Проекты изображений (фильтры, текстуры, режимы изображения)
Математический проект (алгоритм, границы, настройка и общие методы расчета)
Основной программный проект
Позвольте мне привести пример. Я также займусь программированием
открытого интерфейса IFilter
.
{
Применить растровое изображение (растровое изображение);
}
Для иллюстрации я также займусь интерфейсно-ориентированным программированием. Каждый фильтр должен реализовывать этот интерфейс. Определение интерфейса также включает определение оправдания, которое не генерирует реальные изображения, а только генерирует двоичные объекты, которые пока не будут рассматриваться. существование. Возьмите инвертированный цветовой фильтр в качестве примера.
Применить общедоступное растровое изображение ( Bitmap srcImg )
{
// получаем размер исходного изображения
int ширина = srcImg.Width;
int высота = srcImg.Height;
PixelFormat fmt = ( srcImg.PixelFormat == PixelFormat.Format8bppIndexed ) ?
PixelFormat.Format8bppIndexed : PixelFormat.Format24bppRgb
// блокировка исходных данных растрового изображения
;
BitmapData srcData = srcImg.LockBits(
новый прямоугольник (0, 0, ширина, высота),
ImageLockMode.ReadOnly, fmt );
// создаем новое изображение
Растровое изображение dstImg = ( fmt == PixelFormat.Format8bppIndexed )?
AForge.Imaging.Image.CreateGrayscaleImage(ширина, высота):
new Bitmap( width, height, fmt
// блокируем данные растрового изображения назначения
);
BitmapData dstData = dstImg.LockBits(
новый прямоугольник (0, 0, ширина, высота),
ImageLockMode.ReadWrite, fmt );
// копируем изображение
Win32.memcpy(dstData.Scan0, srcData.Scan0, srcData.Stride * height);
// обрабатываем фильтр
ProcessFilter(dstData, fmt);
// разблокируем оба изображения
dstImg.UnlockBits(dstData);
srcImg.UnlockBits(srcData);
вернуть dstImg;
}
Это вход в метод фильтра, который завершает подготовительную работу перед обработкой. ProcessFilter также вызывает метод ProcessFilter, общий для каждого класса фильтра, и этот ProcessFilter является ключом к реализации функции, пошаговой операции или операции по шаблону.
// Обработка фильтра
частный небезопасный void ProcessFilter (данные BitmapData, PixelFormat fmt)
{
int ширина = data.Width;
int height = data.Height;
int lineSize = ширина * ( ( fmt == PixelFormat.Format8bppIndexed ) ? 1 : 3 );
int offset = data.Stride - lineSize
// выполняем работу
;
byte * ptr = (byte *) data.Scan0.ToPointer()
// инвертировать
for ( int y = 0; y < высота; y++ )
{
for (int x = 0; x <lineSize; x++, ptr++)
{
// инвертируем каждый пиксель
*ptr = (байт)( 255 - *ptr);
}
ПТР += смещение;
}
}
Среди них не стоит слишком беспокоиться о Format8bppIndexed. Лично я считаю, что не стоит рассматривать вопрос совместимости с ним на ранних стадиях проектирования.
Теперь давайте поговорим о текстурах. Раньше я особо не задумывался об этом, но обнаружил, что иностранцам нравится с этим экспериментировать, потому что текстуры имеют больше возможностей для игры в математике, и я не знаю, как они это придумали. быть правдой, основанной на воображении. Возможно, один из них обнаружил этот метод, играя в программное обеспечение для математического моделирования, поэтому учитель математики высокого уровня отказался кого-либо принять и очень усердно играл с алгоритмом. Во всяком случае, я думаю, именно это и произошло. . .
общедоступный интерфейс ITextureGenerator
{
/**//// <сводка>
/// Генерируем текстуру
/// </сводка>
float[,] Generate(ширина int, высота int );
/**//// <summary>
/// Reset - перегенерировать внутренние случайные числа
/// </сводка>
недействительный сброс ();
}
Это интерфейс реализации генератора текстур. Чтобы текстура каждый раз была разной, в качестве параметров расчета необходимо обновлять случайные числа.
частный Math.PerlinNoise шум = новый Math.PerlinNoise( 1.0 / 32, 0.05, 0.5, 8 );
Для достижения детализации текстур также требуется шум, поэтому необходимо реализовать множество типов шума.
//Конструкторы
public WoodTexture( ): this( 12.0 ) { }
общественная WoodTexture (двойные кольца)
{
this.rings = кольца;
Перезагрузить( );
}
Конструктор обеспечивает установку значения по умолчанию, которое является пределом размера текстуры модуля.
//Генерируем текстуру
public float[,] Generate(ширина int, высота int)
{
float[,] текстура = новый float[высота, ширина];
int w2 = ширина/2;
int h2 = высота/2
для (int y = 0; y < высота; y++)
{
for (int x = 0; x <ширина; x++)
{
двойной xv = (двойной) (x - w2)/ширина;
двойной yv = (двойной) ( y - h2 ) / высота
текстуры [y, x] =
;
Math.Max( 0.0f, Math.Min( 1.0f, (с плавающей запятой)
Math.Abs( Math.Sin(
( Math.Sqrt( xv * xv + yv * yv ) + Noise.Function2D( x + r, y + r ) )
* Math.PI * 2 * кольца
))
));
}
}
вернуть текстуру;
}
Вот и все. . . Результат моей плохой математики. Я даже не знаю, о чем она говорит, из минимального значения выбираю максимальное. Алгоритмы найти несложно, главное — увидеть, как структура их интегрирует.
общественный недействительный сброс( )
{
г = rand.Next(5000);
}Не забывайте об этом случайном числе, изображение числа также нуждается в естественной красоте.
Объектно-ориентированную концепцию в математической инженерии реализовать непросто. Взгляните на PerlinNoise, чтобы вдохновить других.
public PerlinNoise (двойная initFrequency, двойная initAmplitude, двойная персистентность, целые октавы)
{
this.initFrequency = initFrequency;
this.initAmplitude = initAmplitude;
this.persistance = постоянство;
this.octaves = октавы;
}
Во-первых, нам нужно собрать данные, поскольку обработка изображений включает в себя одномерные и двумерные ситуации, поэтому базовые методы, такие как шум, должны предоставлять соответствующие методы для двух ситуаций.
/**//// <сводка>
/// 1D функция шума Перлина
/// </сводка>
общественная двойная функция (двойной x)
{
двойная частота = initFrequency;
двойная амплитуда = initAmplitude;
двойная сумма = 0;
// октавы
for (int i = 0; i <октавы; i++)
{
сумма += SmoothedNoise( x * частота) *
частота * = 2;
амплитуда *= постоянство;
}
сумма возврата;
}
/**//// <сводка>
/// 2D функция шума Перлина
/// </сводка>
общественный двойной Function2D (двойной x, двойной y)
{
двойная частота = initFrequency;
двойная амплитуда = initAmplitude;
двойная сумма = 0;
// октавы
for (int i = 0; i <октавы; i++)
{
sum += SmoothedNoise( x * частота, y * частота) * амплитуда
* = 2;
амплитуда *= постоянство;
}
сумма возврата;
}
В чем разница между одномерным и двумерным? Когда я учился в средней школе, я узнал, что движение линий создает поверхности. Когда я учился в колледже, я узнал, что циклические и изменяющиеся линии могут представлять собой поверхности. распознавание краев и повышение резкости, я также обнаружил, что раньше недооценивал линию, но на самом деле у нее всего на один параметр меньше, чем у поверхности.
/**//// <сводка>
/// Обычная функция шума
/// </сводка>
защищенный двойной шум (int x)
{
int n = ( x << 13 ) ^ x;
return ( 1.0 - ( ( n * ( n * n * 15731 + 789221 ) + 1376312589 ) & 0x7fffffff ) / 1073741824.0 );
}
защищенный двойной шум (int x, int y)
{
интервал п = х + у * 57;
n = ( n << 13 ) ^ n ;
return ( 1.0 - ( ( n * ( n * n * 15731 + 789221 ) + 1376312589 ) & 0x7ffffff ) / 1073741824.0 );
} Еще раз подтверждает предыдущий абзац. Лично я считаю, что это x+y*57 имеет своего рода проекционный смысл. Получите соответствующее значение шума. Но шум нельзя использовать напрямую.
/**//// <сводка>
/// Сглаженный шум
/// </сводка>
защищенный двойной SmoothedNoise (двойной x)
{
интервал xInt = (int) x;
двойной xFrac = x - xInt;
return CosineInterpolate(Noise(xInt), Noise(xInt + 1), xFrac);
}
защищенный двойной SmoothedNoise(двойной x, двойной y)
{
интервал xInt = (int) x;
интервал yInt = (int) y;
двойной xFrac = x - xInt;
double yFrac = y - yInt
// получаем четыре значения шума;
двойной x0y0 = Шум (xInt, yInt);
двойной x1y0 = Шум (xInt + 1, yInt);
двойной x0y1 = Шум (xInt, yInt + 1);
double x1y1 = Noise( xInt + 1, yInt + 1)
// интерполяция x
;
двойной v1 = CosineInterpolate(x0y0, x1y0, xFrac);
двойной v2 = CosineInterpolate(x0y1, x1y1, xFrac);
//yинтерполяция
вернуть CosineInterpolate(v1, v2, yFrac);
} Плавный шум, название которого кажется несколько неуместным, работает посредством косинусной интерполяции, а не дискретного косинуса. Что такое косинусная интерполяция? /**//// <сводка>
/// Косинусная интерполяция
/// </сводка>
защищенный двойной CosineInterpolate (двойной x1, двойной x2, двойной a)
{
двойной f = (1 - Math.Cos(a * Math.PI)) * 0,5;
return x1 * (1 - f) + x2 * f;
}Всё, есть некоторые вещи, которые мастеру достаточно знать, и можно просто делать их соответственно. Почему? Потому что вы, возможно, не поймете этого при жизни, но кто-то, естественно, разберется, и знания все равно передаются. Точно так же, как вам не нужно знать соотношение кислоты в желудке, чтобы наслаждаться вкусной и острой пищей, вам не нужно беспокоиться о расстройстве желудка для будущих поколений. Не нужно форсировать некоторые вещи, это немного негативно, хаха.
Изображение несложное, если вы понимаете вызывающую связь, и я думаю, что плавающая форма, такая как Photoshop, является лучшим выбором // Инвертировать изображение
Private void invertColorFiltersItem_Click (отправитель объекта, System.EventArgs e)
{
ApplyFilter (новый Invert());
}
// Применяем фильтр к изображению
частная пустота ApplyFilter (фильтр IFilter)
{
пытаться
{
// устанавливаем курсор ожидания
this.Cursor = Cursors.WaitCursor
// применяем фильтр к изображению;
Растровое изображение newImage = filter.Apply(image
if (host.CreateNewDocumentOnChange)
;
{
// открываем новое изображение в новом документе
хост.НовыйДокумент(новоеИзображение);
}
еще
{
если (хост.RememberOnChange)
{
// резервное копирование текущего изображения
если (резервная копия!= ноль)
резервное копирование.Dispose();
резервная копия = изображение;
}
еще
{
// выпускаем текущее изображение
изображение.Dispose();
}
Изображение = новое изображение
// обновление
;
ОбновитьНовоеИзображение();
}
}
поймать (АргументИсключение)
{
MessageBox.Show("Выбранный фильтр нельзя применить к изображению", "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
окончательно
{
// восстанавливаем курсор
this.Курсор = Курсоры.По умолчанию;
}
}Если вызов проходит гладко, никакой объем кода не будет выглядеть беспорядочным. Новичкам следует эффективно использовать регионы.
Существует также концепция DocumentsHost. Ее очень удобно использовать для размещения файлов изображений и подключения изображений и форм. /**//// <summary>.
/// Интерфейс IDocumentsHost
/// Обеспечивает соединение между документами и основным окном
/// </сводка>
общедоступный интерфейс IDocumentsHost
{
bool CreateNewDocumentOnChange{get;}
bool RememberOnChange{get;}
bool NewDocument(растровое изображение);
bool NewDocument (изображение ComplexImage);
Bitmap GetImage (отправитель объекта, строковый текст, размер, формат PixelFormat);
}Все желающие могут обсудить со мной