Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Бот для аркады. Часть №2: подключаем OpenCv

Anna | 17.06.2014 | нет комментариев

Вступление

Продолжаем руководить миром разрабатывать бота для игры. Предшествующий разговор остановился на том, что контакт установлен: мышь бегает, изображение получается. Пора выделять объекты, это требует применение множества разных функции по обработке изображений. Дозволено, безусловно, изобрести велосипед и написать эти функции независимо, но отличнее воспользоваться готовыми наработками.

Для обработки изображений возьмем широкораспространенную библиотеку OpenCv. Она неродная (unmanaged) для .net, следственно подключим ее через wrapper OpenCvSharp.

OpenCv нам необходима для того, Дабы, применяя разные реформирования к изображениями, предпочесть такое реформирование, которое отделит фон и тени от объектов, а объекты друг от друга. К этой цели и будем сегодня двигаться.

Затрагиваемые темывыбор библиотеки для обработки изображений, выбор wrapper-а для работы с OpenCv, основные функции OpenCv, выделение движущихся объектов, цветовая модель HSV.

Отчего OpenCv?

Для обработки изображений есть и другие отличные библиотеки, на одной OpenCv свет клином не сошелся.

При программировании под .net также стоит обратить внимание на библиотеку Accord.Net (и ее больше раннюю версию AForge.Net). Эти две библиотеки также бесплатные, но родные(managed) для .net-платформы, в различии от OpenCv.

При разработке продукта на продажу, а не «for fun» — я скорее остановился бы на managed-библиотеках. Выбор отличных managed-библиотек не ухудшает продуктивность, но здорово упрощает развертывание, переносимость и дальнейшее сопровождение. Но в стартапах и при разработке «for fun» значимей огромное community, чем грядущее облегчение сопровождения. И это возвращает нас к библиотеке OpenCv, у которой гораздо больше широкая популярность.

Огромное community

При независимом выборе библиотек имеет толк ориентироваться на те, что имеют огромное энергичное community. Огромное число сторонников дает высокую оттестированность библиотеки, малый процент неудобств применения, многообразие документации, и делает простым гугление вопросов, возникающих по ходу работы.

OpenCv целиком поддерживает данный принцип. По ней есть книжка: Learning OpenCv (Добросовестно говоря, еще не читал, но собираюсь поправить это в ближайщем времени), есть ее перевод на русский locv.ru/(теперь у меня не открывается), есть online-документация, есть куча вопросов с результатами наstackoverflow.

Всё это наем дает стремительный старт, обеспечивая подход «Пли! Готовсь! Целься» (когда постижение библиотеки идет по ходу работы) взамен классического «Готовсь! Целься! Пли!» (когда вначале существенное время уходит на заблаговременное ознакомление с устройством библиотеки).

Гуглим вопросы

Множество задач решалось с поддержкой стремительного гугления: в гугл забиваются ключевые слова в тему вопроса, и в первых ссылках сразу же находится результат. При независимом поиске результатов непременно обращаем внимание на то, какие ключевые слова применяются для изложения желанной темы. Гуглу не главна красота и граматическая правильность вопроса, для него важно только присутствие ключевых слов, по которым он и будет выбирать результат.

Примеры решаемых вопросов:
— выделить R-компоненту из изображения — запрос в гугл: google: opencv get single channel, и первая же ссылка говорит, что это делается с поддержкой функции Split
— обнаружить отличия между изображениями: google: opencv difference images, и пример из первого же результата говорит, что это делает функция absdiff. Если же при поиске использовать слово compare взамен difference, то гугл начнет показывать вовсе другие страницы, и это даст больше всеобщие результаты с рекомендацией применять сопоставление гистограмм и т.д. Это показывает значимость выбора ключевых слов при поиске результата на свой вопрос.

Выбираем .net-wrapper для OpenCv

Библиотеку предпочли, осталось подружить ее с C#-ом. Эта задача теснее решена до нас, и нам вновь остается только сделать выбор между имеющимися вариантами. Распространенных wrapper-а два: Emgu Cv иOpenCvSharp. Emgu Cv больше ветхая и больше кондовая, OpenCvSharp больше современная. Выбор остановился на OpenCvSharp, купили слова автора о том, что поддерживается IDisposable. Это обозначает, что автор не легко 1 в 1 перенес конструкции и функции из C/C в C#, но и допилил их напильником, Дабы их было комфортнее применять в C#-жанре написания кода.

Подключаем OpenCvSharp к плану

Подключение OpenCvSharp к плану делается стандартным методом, без каких-то специальных закавык. Есть маленький tutorial от автора, также есть вероятность подключения OpenCvSharp через nuget.

Основные базовые функции

OpenCv имеет уйма функции для работы с изображениями. Остановимся только на основных базовых функциях, которые применяются при решении задачи выделения объектов из изображения. OpenCv имеет два варианта применения: C-жанр и C -жанр. Для облегчения кода будем применять C -жанр (правильней его аналог через OpenCvSharp).

Основных класса два: Mat и Cv2. Оба находятся в namespace-е OpenCvSharp.CPlusPlus. Mat — это само изображение, а Cv2 — это комплект действий над изображениями.
Функции:

//загрузка изображения
var mat = new Mat("test.bmp");

//сохранение изображения
mat.ImWrite("out.bmp");

//преобразование в bitmap
var bmp = mat.ToBitmap();

//преобразование из bitmap (требуется подключение OpenCvSharp.Extensions)
var mat2 = new Mat(bmp.ToIplImage(), true); 

//показ изображения
using (new Window("изображение", mat))
{
   Cv2.WaitKey();
}

//преобразование цветового пространства (скажем, из цветного в градации серого)
Cv2.CvtColor(mat, dstMat, ColorConversion.RgbToGray);

//разделение изображения на отдельные цветовые каналы
Cv2.Split(mat, out mat_channels)

//сборка многоцветного изображения из отдельных каналов
Cv2.Merge(mat_channels, mat)

//разница между двумя изображениями
Cv2.Absdiff(mat1, mat2, dstMat);

//приведение точек, которые темнее/светлее определенного яруса(50) к черному(0) либо белому цвету(255)
Cv2.Threshold(mat, dstMat, 50, 255, OpenCvSharp.ThresholdType.Binary);

//рисование примитивов
mat.Circle(x, y, radius, new Scalar(b, g, r));
mat.Line(x1, y1, x2, y2, new CvScalar(b, g, r));
mat.Rectangle(new Rect(x, y, width, height), new Scalar(b, g, r));
mat.Rectangle(new Rect(x, y, width, height), new Scalar(b, g, r), -1); //закрашенный прямоугольник
mat.PutText("test", new OpenCvSharp.CPlusPlus.Point(x, y), FontFace.HersheySimplex, 2, new Scalar(b, g, r))

Также в OpenCv есть особые функции для выделения объектов(Structural Analysis and Shape DescriptorsMotion Analysis and Object TrackingFeature DetectionObject Detection), но наскоком выжать из них пригодный итог не получилось (нужно, вероятно, все-таки книжку почитать), следственно оставим их на потом.

Выделение объектов

Примитивный способ выделения объектов сводится к придумыванию фильтра, тот, что отсечет фон от объектов, а объекты друг от друга. К сожалению, изображение поля Zuma-ы дюже пестрое, и простая отсечка по яркости не срабатывает. Ниже приведены начальное изображение, ее черно-белый вариант, и лесенка разных отсечек. На последнем изображении видно, что во всех случаях фон сливается с шарами, либо либо и то, и другое присутствует, либо единовременно — отсутствуют.

Выделение объектов крепко затрудняет их «полосатость». Вот, скажем, как реагирует функция Canny, выделяющая силуэты объектов.

Применение отдельных цветовых компонент жизнь отличнее не делает.

Выделение движущихся объектов

Основа выделения движушихся объектов — примитивна: сравниваются два файла — изменившиеся точки и являются желанными объектами. На практике всё труднее, и демон, как неизменно, кроется в мелочах…

Образование серии изображении

Для образования серии изображений добавим маленький код в нашего бота. Бот будет вести историю последних кадров, и по нажатию на пробел сбрасывать их на диск.

      var history = new List<Bitmap>();
      for (var tick = 0; ;tick  )
      {
          var bmp = GetScreenImage(gameScreenRect);
          history.Insert(0, bmp);
          const int maxHistoryLength = 10;
          if (history.Count > maxHistoryLength)
            history.RemoveRange(maxHistoryLength, history.Count - maxHistoryLength);

          if (Console.KeyAvailable)
          {
            var keyInfo = Console.ReadKey();
            if (keyInfo.Key == ConsoleKey.Spacebar)
            {
              for (var i = 0; i < history.Count;   i)
                history[i].Save(string.Format("{0}.png", i));
            }
            [..]
          }
          [..]
      }

Запускаем, жмем и вуаля — у нас на руках есть два кадра.

Сопоставление соседних

Вычитаем одно изображение из другого… и что именуется «смешались в кучу кони, люди». Шары превратились во что-то «необычное» (это отлично видно, на полноразмерном фрагменте), но это хорошо, основное что шары получилось отделить от фона.

Сопоставление со намеренно подготовленным фоном

Сопоставление соседних кадров отлично подходит для «отдельно стоящих» движущихся объектов. Если же нужно выделить плотно движущиеся объекты, то отличнее работает сопоставление со намеренно подготовленным неподвижным фоном.
Подготавливаем такой фон:

Сопоставляем:


Много отличнее, но всё портят тени. От теней я пытался избавиться большинством методов, но самый наилучший результат дала мысль, что тень, по сути, есть метаморфоза яркости, а это теснее меня навело на мысль о цветовой модели HSV.

HSV

Цветовая модель HSV, также как и RGB, состоит из 3 каналов. Но в различии от него (и того же CMYK) — это не легко смешение цветов.
— 1-й канал, H(Hue) — цветовой тон. В первом приближении, это номер цвета из радуги.
— 2-й канал, S(Saturation) — концентрация. Чем поменьше значение этого канала, тем цвет ближе к серому, чем огромнее — тем больше цвет выражен. Цвета с высокой насыщенностью вестимы под разговорным наименованием — «кислотные».
— 3-й канал, V(Value) — яркость. Это самый простый для понимания канал, чем огромнее освещенность, тем выше значение по данному каналу.
Картинка справа показывает связь каналов и цветов между собой. По кругу идет радуга — это канал H. Треугольник для определенного цвета (теперь это алый) показывает метаморфоза канала S — насыщенности (направление в верх-право) и метаморфоза канала V — яркости (в верх-лево). Классически, значения канала H лежат в диапазоне 0-360, S — 0-100, V — 0-100. В OpenCv значения всех каналов приведены к диапазону 0-255 для того, Дабы по-максимуму применять размерность одного байта.

Цветовая модель RGB близка к человеческому глазу, к тому как он устроен. Цветовая k!, LoadMode.GrayScale); var src_1 = new Mat(“1.bmp”); var src_1_g = new Mat(“1.bmp”, LoadMode.GrayScale); var background = new Mat(“background.bmp”); var background_g = new Mat(“background.bmp”, LoadMode.GrayScale); src.Resize(resizeK).ImWrite(dir “0.png”); src_g.Resize(resizeK).ImWrite(dir “0 g.png”); src_g.ThresholdStairs().Resize(resizeK).ImWrite(dir “0 g th.png”); var canny = new Mat(); Cv2.Canny(src_g, canny, 50, 200); canny.Resize(0.5).ImWrite(dir “0 canny.png”); Mat[] src_channels; Cv2.Split(src, out src_channels); for (var i = 0; i < src_channels.Length; i) { var channels = Enumerable.Range(0, src_channels.Length).Select(j => new Mat(src_channels[0].Rows, src_channels[0].Cols, src_channels[0].Type())).ToArray(); channels[i] = src_channels[i]; var dst = new Mat(); Cv2.Merge(channels, dst); dst.Resize(resizeK).ImWrite(dir string.Format(“0 ch{0}.png”, i)); src_channels[i].ThresholdStairs().Resize(resizeK).ImWrite(dir string.Format(“0 ch{0} th.png”, i)); } if (true) { src.Resize(0.4).ImWrite(dir “0.png”); src_1.Resize(0.4).ImWrite(dir “1.png”); background.Resize(0.4).ImWrite(dir “bg.png”); var dst_01 = new Mat(); Cv2.Absdiff(src, src_1, dst_01); dst_01.Resize(resizeK).ImWrite(dir “01.png”); dst_01.Cut(new Rect(50, src.Height * 4 / 5 – 50, src.Width / 5, src.Height / 5)).ImWrite(dir “01 part.png”); dst_01.Cut(new Rect(50, src.Height * 4 / 5 – 50, src.Width / 5, src.Height / 5)).CvtColor(ColorConversion.RgbToGray).ImWrite(dir “01 g.png”); dst_01.CvtColor(ColorConversion.RgbToGray).ThresholdStairs().Resize(resizeK).ImWrite(dir “01 g th.png”); var dst_01_g = new Mat(); Cv2.Absdiff(src_g, src_1_g, dst_01_g); dst_01_g.Cut(new Rect(50, src.Height * 4 / 5 – 50, src.Width / 5, src.Height / 5)).ImWrite(dir “0g1g.png”); dst_01_g.ThresholdStairs().Resize(resizeK).ImWrite(dir “0g1g th.png”); } if (true) { var dst_0b = new Mat(); Cv2.Absdiff(src, background, dst_0b); dst_0b.Resize(0.6).ImWrite(dir “0b.png”); var dst_0b_g = new Mat(); Cv2.Absdiff(src_g, background_g, dst_0b_g); dst_0b_g.Resize(0.3).ImWrite(dir “0b g.png”); dst_0b_g.ThresholdStairs().Resize(0.3).ImWrite(dir “0b g th.png”); } if (true) { var hsv_src = new Mat(); Cv2.CvtColor(src, hsv_src, ColorConversion.RgbToHsv); var hsv_background = new Mat(); Cv2.CvtColor(background, hsv_background, ColorConversion.RgbToHsv); var hsv_background_channels = hsv_background.Split(); var hsv_src_channels = hsv_src.Split(); if (true) { var all = new Mat(src.ToIplImage(), true); for (var i = 0; i < hsv_src_channels.Length; i) { hsv_src_channels[i].CvtColor(ColorConversion.GrayToRgb).CopyTo(all, new Rect(i * src.Width / 3, src.Height / 2, src.Width / 3, src.Height / 2)); } src_g.CvtColor(ColorConversion.GrayToRgb).CopyTo(all, new Rect(src.Width / 2, 0, src.Width / 2, src.Height / 2)); all.Resize(0.3).ImWrite(dir “all.png”); } foreach (var pair in new[] { “h”, “s”, “v” }.Select((channel, index) => new { channel, index })) { var diff = new Mat(); Cv2.Absdiff(hsv_src_channels[pair.index], hsv_background_channels[pair.index], diff); diff.Resize(0.3).With_Title(pair.channel).ImWrite(dir string.Format(“0b {0}.png”, pair.channel)); diff.ThresholdStairs().Resize(0.3).ImWrite(dir string.Format(“0b {0} th.png”, pair.channel)); hsv_src_channels[pair.index].Resize(resizeK).With_Title(pair.channel).ImWrite(dir string.Format(“0 {0}.png”, pair.channel)); foreach (var d in new[] { -100, -50, 50, 100 }) { var delta = new Mat(hsv_src_channels[pair.index].ToIplImage(), true); delta.Rectangle(new Rect(0, 0, delta.Width, delta.Height), new Scalar(Math.Abs(d)), -1); var new_channel = new Mat(); if (d >= 0) Cv2.Add(hsv_src_channels[pair.index], delta, new_channel); else Cv2.Subtract(hsv_src_channels[pair.index], delta, new_channel); var new_hsv = new Mat(); Cv2.Merge(hsv_src_channels.Select((channel, index) => index == pair.index ? new_channel : channel).ToArray(), new_hsv); var res = new Mat(); Cv2.CvtColor(new_hsv, res, ColorConversion.HsvToRgb); res.Resize(resizeK).With_Title(string.Format(“{0} {1: #;-#}”, pair.channel, d)).ImWrite(dir string.Format(“0 {0}{1}.png”, pair.channel, d)); } } } static class OpenCvHlp { public static Scalar ToScalar(this Color color) { return new Scalar(color.B, color.G, color.R); } public static void CopyTo(this Mat src, Mat dst, Rect rect) { var mask = new Mat(src.Rows, src.Cols, MatType.CV_8UC1); mask.Rectangle(rect, new Scalar(255), -1); src.CopyTo(dst, mask); } public static Mat Absdiff(this Mat src, Mat src2) { var dst = new Mat(); Cv2.Absdiff(src, src2, dst); return dst; } public static Mat CvtColor(this Mat src, ColorConversion code) { var dst = new Mat(); Cv2.CvtColor(src, dst, code); return dst; } public static Mat Threshold(this Mat src, double thresh, double maxval, ThresholdType type) { var dst = new Mat(); Cv2.Threshold(src, dst, thresh, maxval, type); return dst; } public static Mat ThresholdStairs(this Mat src) { var dst = new Mat(src.Rows, src.Cols, src.Type()); var partCount = 10; var partWidth = src.Width / partCount; for (var i = 0; i < partCount; i) { var th_mat = new Mat(); Cv2.Threshold(src, th_mat, 255 / 10 * (i 1), 255, ThresholdType.Binary); th_mat.Rectangle(new Rect(0, 0, partWidth * i, src.Height), new Scalar(0), -1); th_mat.Rectangle(new Rect(partWidth * (i 1), 0, src.Width – partWidth * (i 1), src.Height), new Scalar(0), -1); Cv2.Add(dst, th_mat, dst); } var color_dst = new Mat(); Cv2.CvtColor(dst, color_dst, ColorConversion.GrayToRgb); for (var i = 0; i < partCount; i) { color_dst.Line(partWidth * i, 0, partWidth * i, src.Height, new CvScalar(50, 200, 50), thickness: 2); } return color_dst; } public static Mat With_Title(this Mat mat, string text) { var res = new Mat(mat.ToIplImage(), true); res.Rectangle(new Rect(res.Width / 2 – 10, 30, 20 text.Length * 15, 25), new Scalar(0), -1); res.PutText(text, new OpenCvSharp.CPlusPlus.Point(res.Width / 2, 50), FontFace.HersheyComplex, 0.7, new Scalar(150, 200, 150)); return res; } public static Mat Resize(this Mat src, double k) { var dst = new Mat(); Cv2.Resize(src, dst, new OpenCvSharp.CPlusPlus.Size((int)(src.Width * k), (int)(src.Height * k))); return dst; } public static Mat Cut(this Mat src, Rect rect) { return new Mat(src, rect); } public static Mat[] Split(this Mat hsv_background) { Mat[] hsv_background_channels; Cv2.Split(hsv_background, out hsv_background_channels); return hsv_background_channels; } }

Бот для DirectX-аркады. Часть №1: устанавливаем контакт
Бот для аркады. Часть №2: подключаем OpenCv

 

Источник: programmingmaster.ru

Оставить комментарий
БАЗА ЗНАНИЙ
СЛУЧАЙНАЯ СТАТЬЯ
СЛУЧАЙНЫЙ БЛОГ
СЛУЧАЙНЫЙ МОД
СЛУЧАЙНЫЙ СКИН
НОВЫЕ МОДЫ
НОВЫЕ СКИНЫ
НАКОПЛЕННЫЙ ОПЫТ
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB