Балуемся с камерой

Добавлен , опубликован
Программирование
Язык:
C#
Добрый вечер.
Недавно откопал у себя несколько ненужных веб-камер и решил побаловать с ними, например написав пару прог. Про одну из них расскажу сегодня, про остальные позже.

Виртуальный стол

Забавная вещь, вам скажу, если вам нечем заняться, хочется сделать планшет для страдания фигней, то это то что вам нужно. Для этого понадобиться
  • веб камера
  • карандаш
  • изолента
  • лист бумаги
  • Visual Studio 2010

Как работает

Ниже вы найдете скриншоты каждого этапа алгоритма, а так же немного полезного кода. А тут я расскажу без иллюстраций.

Первый этап

На первом этапе надо получить картинку с нашей веб камеры. Для этого я нашел библиотеку WebCam_Capture, скаченную с какого-то левого сайта, когда-то давно. Работать с ней очень просто, хоть она и корявая, но для отладки таких приложений этого достаточно
Вот собственно инициализация камеры
webCam = new WebCamCapture();
webCam.ImageCaptured += new WebCamCapture.WebCamEventHandler(webCam_ImageCaptured);
И обработка полученного изображения
        void webCam_ImageCaptured(object source, WebcamEventArgs e)
        {
            Image img = e.WebCamImage;

            vDesctop.ProcessImage(img, ref img);

            if (img != null)
            {
                //Выводим картинку на экран
                outputZone.Image = img;
            }
        }

Второй этап

Как видно из прошлого кусочка кода, я использую такой код для обработки изображения
vDesctop.ProcessImage(img, ref img);
vDesctop - это такой специальный класс, для управления процессом отслеживания нашего, так называемого стилуса
ProcessImage - метод для обработки картинки. Первый параметр - это входное изображение, а второй это указатель на изображение, куда будет записываться результат обработки.
Теперь вернемся к сути, данного этапа. На этом этапе мы вырежем необходимую зону, оставим для обработки, только наш листок, остальное нам будет только мешать. Для этого перебираем каждый пиксель картинки и проверяем на вхождения в четырех угольник, указанный заранее. Спасибо местным умельцам ( Hanabishi) за написание полезных функций на jass, мне оставалось лишь их портировать на C#
портированные функции
        public static double TriS (double x1, double y1, double x2, double y2, double x3, double y3)
        {
            return Math.Abs(x1*(y2-y3)+x2*(y3-y1)+x3*(y1-y2))/2;
        }

        //Принадлежность точки (x;y) треугольнику (x1;y1);(x2;y2);(x3;y3)
        public static bool IsCoordsInTriangle(double x, double y, double x1, double y1, double x2, double y2, double x3, double y3)
        {
            return (int)TriS(x1, y1, x2, y2, x3, y3) == (int)(TriS(x1, y1, x2, y2, x, y)+TriS(x2, y2, x3, y3, x, y)+TriS(x1, y1, x3, y3, x, y));
        }

        //Принадлежность точки (x;y) произвольному четырёхугольнику (x1;y1);(x2;y2);(x3;y3);(x4;y4). Облегчённая версия, см. примечание.
        public static bool IsCoordsIn4GonSimple(double x, double y, double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4)
        {
            return IsCoordsInTriangle(x, y, x1, y1, x2, y2, x3, y3) || IsCoordsInTriangle(x, y, x1, y1, x4, y4, x3, y3);
        }

        //Принадлежность точки (x;y) произвольному четырёхугольнику (x1;y1);(x2;y2);(x3;y3);(x4;y4). Полная версия, см. примечание.
        public static bool IsCoordsIn4Gon(double x, double y, double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4)
        {
            return IsCoordsInTriangle(x, y, x1, y1, x2, y2, x3, y3) || IsCoordsInTriangle(x, y, x1, y1, x4, y4, x3, y3) || IsCoordsInTriangle(x, y, x1, y1, x2, y2, x4, y4);
        }
В итоге, каждый попавший пиксель мы не трогаем, а все остальные закрашиваем другим цветом, чтобы не мешали

Третий этап

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

Четвертый этап

На этом этапе мы находим искомый цвет. Для этого перебираем каждый доступный пиксель и сравниваем с заданным заранее цветом. Если он проходит наш фильтр, то суммируем координаты. Тоесть на этом этапе мы находим средние координаты всех пикселей прошедших наш фильтр.

Пятый этап

Один из самых сложных. На этом этапе наша задача преобразовать координаты из координатной плоскости камеры, в координатную плоскость выделенного прямоугольника. Я долго думал, как это сделать в результате нашел очень оригинальное решение. Код данного преобразования смотрите в приложении. А здесь я лишь расскажу как оно действует.
И так. Что у нас есть. А у нас есть координаты вершин произвольного четырехугольника, задающего границы нашей координатной плоскости. Так же есть координаты точки, которую надо преобразовать. А теперь сплошная математика.
Сперва задаем границы нашего пространства с помощью 4 векторов (top - верхняя граница, bottom - нижняя граница, left - левая граница, right - правая граница). Дальше проводим 2 прямые line1 и line2 (line1 - проведенная из верхнего левого угла в нашу точку и line2 - проведенная из нижнего левого угла в нашу точку). Находим точки пересечения этих прямых с границами top и bottom (line1 пересекаем с bottom, а line2 с top). В результате получаем 2 точки. Дальше получаем вектора отрезков от левых углов, на которой лежит эта точка, до самих точек. Находим заманчивое отношение между длиной данного отрезка и длиной отрезка, на которой точка лежит. Дальше задаем границы нового пространства (В своем случае я нахожу координаты экрана, поэтому задаю экранное пространство) с помощью тех же 4 векторов. Теперь восстанавливаем длину отрезка из отношения, которое мы выяснили чуть ранее. Восстанавливаем координаты точек пересечения линий line1 и line2. Теперь мы можем построить наши линии line1 и line2. Строим их. Находим точку пересечения этих прямых, а эта точка является искомой. Вот и все. Вот такая вот математическая порнография. К сожалению фотографию своих вычислений на листке не могу предоставить, они почему то не хотят опять заливаться.

Приложение

хитрый алгоритм преобразования координат
            Vector2D LT = new Vector2D(rect[0].X, rect[0].Y);
            Vector2D LB = new Vector2D(rect[1].X, rect[1].Y);
            Vector2D RB = new Vector2D(rect[2].X, rect[2].Y);
            Vector2D RT = new Vector2D(rect[3].X, rect[3].Y);

            Vector2D top = new Vector2D(LT, RT);
            Vector2D bottom = new Vector2D(LB, RB);
            Vector2D left = new Vector2D(LT, LB);
            Vector2D right = new Vector2D(RT, RB);

            Vector2D point = new Vector2D(x, y);

            Vector2D line1 = point - LT;
            Vector2D line2 = point - LB;

            Vector2D crossTop = VectorMath.Cross(LB, line2, LT, top);
            Vector2D crossBottom = VectorMath.Cross(LT, line1, LB, bottom);

            line1 = crossTop - LT;
            double l1 = line1.Length() / top.Length();

            line1 = crossBottom - LB;
            double l2 = line1.Length() / bottom.Length();

            LT = new Vector2D(0, 0);
            RT = new Vector2D(1366, 0);
            LB = new Vector2D(0, 768);
            RB = new Vector2D(1366, 768);

            top = new Vector2D(LT, RT);
            bottom = new Vector2D(LB, RB);
            left = new Vector2D(LT, LB);
            right = new Vector2D(RT, RB);

            line1 = LT + top.GetNormalize() * (l1 * top.Length());
            line1 = line1 - LB;
            line2 = LB + bottom.GetNormalize() * (l2 * bottom.Length());
            line2 = line2 - LT;

            point = VectorMath.Cross(LB, line1, LT, line2);

            CoordX = (int)Math.Floor(point.x);
            CoordY = (int)Math.Floor(point.y);
`
ОЖИДАНИЕ РЕКЛАМЫ...
0
37
12 лет назад
0
Это немного не так делается, но в целом гц =)
Давно хотел этим заняться, и наверно, займусь когда-нибудь
0
29
12 лет назад
0
ScorpioT1000, каждый раз сажусь баловаться с поиском на картинке и каждый раз делаю по разному.
но если есть какие идеи рассказывай
0
37
12 лет назад
Отредактирован ScorpioT1000
0
alexprey, у меня есть собственная капча распознавалка - распознает любые символы по заданной нестрогой маске =) мб прикрутить... хотя на плюсах, но в роли отдельной dll сишной библиотеки:
header
#ifndef CR_LIBRARY_ADAPTER_H
#define CR_LIBRARY_ADAPTER_H
// CR DLL-functions adapter

#include "CR.h"
#include "CRAlgorithmTest1.h"
#define crDLLEXPORT __declspec(dllexport)

class CRLibraryAdapter {
public:

    static void Start(sc::COLORMATRIX mx, size_t w, size_t h, sc::COLORMATRIX advancedMx,
                      sc::COLORMATRIX maskSeq, size_t maskW, size_t maskH);
    /// Starts recognition process. Takes the pixel map as COLORMATRIX, width, height,
    /// and the previous (advanced) color map from series (optional, =NULL if isn't used).
    /// Width/Height of advancedMx is the same as in the first one.
    /// maskSeq - matrix of the image which is represented as a vertical sequence of monospaced parts(mask);
    /// The mask is used as forms of digits to compare with digits on the target image.
    /// If arguments are wrong, GetResult will return ST_ERROR.

    static void Stop();
    /// Stops the process. Use GetState() func to see results.

    static cr::StateType GetState();
    /// Returns the current state of the CR from the StateType enum.
    /// Before first start returns ST_READY.

    static float GetStateProgress();
    /// Returns the progress of the current state as an absolute number (from 0.0 to 1.0).

    static bool GetResult(char * resultBuffer, size_t maxCount);
    /// Gets the captcha recognition result as ascii string. Example: "1234".
    /// If there is the bad argument, the function will return false.

    static bool GetIntermediatePic(sc::COLORMATRIX picBuffer, size_t * widthBuff, size_t * heightBuff);
    /// Gets the process intermediate picture for visual indication of progress.
    /// widthBuff and heightBuff might be equal to NULL, it means that function will use the source size.
    /// If the function fails, the return value is false.

private:
    CRLibraryAdapter() {}; // cannot create object
};

// Export functions
extern "C" {
crDLLEXPORT void Start(unsigned int* mx,size_t w, size_t h,unsigned int* advMx,unsigned int*mask,size_t mw, size_t mh) { CRLibraryAdapter::Start(mx,w,h,advMx,mask,mw,mh); }
crDLLEXPORT void Stop() { CRLibraryAdapter::Stop(); }
crDLLEXPORT int GetState() { return CRLibraryAdapter::GetState(); }
crDLLEXPORT float GetStateProgress() { return CRLibraryAdapter::GetStateProgress(); }
crDLLEXPORT bool GetResult(char* buf,size_t max) { return CRLibraryAdapter::GetResult(buf,max); }
crDLLEXPORT bool GetIntermediatePic(unsigned int* buf,size_t* w,size_t* h) { return CRLibraryAdapter::GetIntermediatePic(buf,w,h); }
}


cr::CRAlgorithmTest1 crAlgorithmObject;
cr::CR crInstance(crAlgorithmObject); // Captcha Recognizer global object

#endif
ну для фана x)
0
29
12 лет назад
0
ScorpioT1000, ну у меня цель была не распознавание капчи, а отслеживание позиции маркера
1
37
12 лет назад
Отредактирован ScorpioT1000
1
а если ты про то как иначе искать на картинке, то там делается в полном непрерывном пространстве с ключевыми точками, тоесть идет обрасть x/y от 0 до 1, ключевые пиксели равномерно распределяются как граф по ней и идет поиск по графу, а при отрисовке просто между ними нужная интерполяция
выглядит это всё как-то так:
т.е. видишь, у каждого узла по 4 связи, кроме крайних, вся идея в том чтобы выделить нужные подграфы и с ними работать по всеизвестным алгоритмам поиска пути и тому подобное. Например, выделить тот же лист бумаги, бахнуть по нему оптимизацией (в идеале должен получиться граф, где узлы - углы листа и карандаша итп) и работать только с ним, без всяких черных масок и пробегов по всей матрице
0
29
12 лет назад
0
ScorpioT1000, кстати когда то давно читал про это, но раньше не допер. Сейчас вроде понял суть. Надо будет как нибудь сесть и перечитать про такие алгоритмы
Чтобы оставить комментарий, пожалуйста, войдите на сайт.