Пример iANPRcapture_motion на C# для iANPR SDK / iANPR SDK / Recog.ru - Распознавание образов для программистов


Пример iANPRcapture_motion на C# для iANPR SDK

1 О предназначении программы

Программа iANPRcapture_motion_CShrp предназначена для демонстрации возможностей iANPR SDK в вычислении траектории движения автомобильного номера и детектирования пересечения номером заранее заданных линий, т.е. для демонстрации возможности реализации функционала детектирования въезда-выезда автомобилей с помощью iANPR SDK. Эта программа написана на языке C# и является аналогом программы iANPRcapture_motion, написанной на языке С++. Эти и другие примеры использования распространяются в составе iANPR SDK.

2 Пример использования

Пример работы программы показан в следующем ролике.



Из ролика видно, что программа оповещает о траектории движения не всех номеров, а только тех, которые пересекли две параллельные линии. В случае монтирования камеры на КПП, эти функции могут быть использованы для детектирования автомобильных номеров, приблизившихся к шлагбауму, в составе программы контроля и учёта допуска на территорию (автоматического принятия решения об открытии/закрытии шлагбаума).

Текущая версия iANPRcapture_motion_CShrp используется так:
iANPRcapture_motion_CShrp.exe 7 D:/video.avi
Где 7 – тип распознаваемого номера (российские номера),
D:/video.avi – полное имя видео файла.

Информацию о распознавании номеров других стран можно найти в документации iANPR SDK (распространяется в составе iANPR SDK), в справке программы и на сайте IntBuSoft.

3 Портирование программы с С++ на С#

Импортирование (объявление) функций из iANPR SDK
Первое, что нужно сделать, чтобы написать программу на языке C#, использующую iANPR SDK, необходимо импортировать (объявить) в программу функции и структуры из соответствующей dll библиотеки. В случае с iANPRcapture_motion_CShrp необходимо импортировать следующие структуры и функции

// Program.cs
// Структуры
public struct ANPR_OPTIONS
        {
            public int min_plate_size; // Минимальная площадь номера
            public int max_plate_size; // Максимальная площадь номера
            public int Detect_Mode; // Режимы детектирования	
            public int max_text_size; // Максимальное количество символов номера + 1
            public int type_number; // Тип автомобильного номера	
            public int flags; // Дополнительные опции
        };

// Функции
[DllImport("iANPRcapture_vc10_x86.dll")]
unsafe public static extern IntPtr CreateiANPRCapture(int max_frames, ANPR_OPTIONS Options, CvRect Rect);
[DllImport("iANPRcapture_vc10_x86.dll")]
unsafe public static extern void ReleaseiANPRCapture(ref IntPtr Capture);

[DllImport("iANPRcapture_vc10_x86.dll")]
unsafe public static extern int CreateMemoryForiANPRCapture(IntPtr Capture, int min_frames_with_plate, int frames_without_plate, int max_plates_in_mem);

[DllImport("iANPRcapture_vc10_x86.dll")]
unsafe public static extern int CreateLineIntersection(IntPtr Capture, CvPoint p1a, CvPoint p2a, CvPoint p1b, CvPoint p2b);

[DllImport("iANPRcapture_vc10_x86.dll")]
unsafe public static extern int AddFrameToiANPRCapture(IntPtr Capture, IntPtr Image, ref int AllNumber, [In, Out] CvRect[] Rects, IntPtr Texts);

[DllImport("iANPRcapture_vc10_x86.dll")]
unsafe public static extern int GetNumbersInMemory(IntPtr Capture, ref int AllNumber, IntPtr Texts, int Size_Texts, [In, Out] CvPoint[] Points, ref int all_point);

[DllImport("iANPRcapture_vc10_x86.dll")]
unsafe public static extern void LicenseCapture(sbyte[] key);


IntPtr – указатель на область памяти, без уточнения содержимого этой памяти. В этом смысле IntPtr близкий аналог типа void*.
Ключевое слово ref означает передачу параметра по ссылке (стандартно параметры передаются по значению).

Например, в С++ функция объявлена как:
void ReleaseiANPRCapture(void**);


Тогда в С# она может быть объявлена так:
unsafe public static extern void ReleaseiANPRCapture(ref IntPtr Capture);


Однако таким же способом (ref) передать двумерный массив возможно, но затруднительно. Ниже будет показан один из способов передачи двумерного массива (массива массивов).
Ключевые слова [In, Out] применяются для корректной передачи одномерного массива по ссылке.
Например, в С++ функция объявлена как:
int AddFrameToiANPRCapture( iANPRCapture /* =void* */ Capture, IplImage* Image, int* AllNumber, CvRect* Rects, char** Texts );


Тогда в С# она может быть объявлена так:
unsafe public static extern int AddFrameToiANPRCapture(IntPtr Capture, IntPtr Image, ref int AllNumber, [In, Out] CvRect[] Rects, IntPtr Texts);


В С++ объявлении последний параметр указан как «указатель на массив указателей» или, иначе говоря, двумерный массив char. В C# объявлении последний параметр указан как IntPtr. Компилятор C# ничего не будет знать о том, что в этом IntPtr должен быть указатель на массив указателей, поэтому требуется самостоятельно помнить о необходимости выделения памяти как под массив указателей, так и под массивы, на которые эти указатели отсылают (под двумерный массив).

В C# такой двумерный массив char можно создать следующим образом:

int all = 100;
IntPtr res = Marshal.AllocHGlobal(all * IntPtr.Size);

for (int ii = 0; ii < all; ii++)
Marshal.WriteIntPtr(res, ii * IntPtr.Size, Marshal.AllocHGlobal(21));


Стоит помнить, что функция Marshal.AllocHGlobal выделяет память в байтах, поэтому чтобы создать массив из 100 указателей, необходимо выделить 100 * IntPtr.Size байт памяти. Функция Marshal.AllocHGlobal(21) выделяет 21 байт памяти и возвращает указатель на эту область, который затем записывается в ранее выделенную память res + ii * IntPtr.Size. Так же стоит помнить, что оператор+ добавляет к значению res единицы (байты). Т.е. 500 + 1 = 501, а не 504 (следующий указатель), как можно было бы ожидать.
После выполнения данного выше участка кода указатель res может передаваться в С++ функцию, которая примет его за двумерный массив char. Но необходимо помнить об освобождении памяти, когда она уже больше не требуется (память, которую мы выделяли, не управляется сборщиком мусора C#). В программе iANPRcapture_motion_CShrp это делается так:

for (int j = 0; j < 100; j++)
{
    IntPtr subString = (IntPtr)Marshal.PtrToStructure(res + j * IntPtr.Size, typeof(IntPtr));
    Marshal.FreeHGlobal(subString);
}
Marshal.FreeHGlobal(res);


Импортирование (объявление) функций из OpenCV
iANPR SDK не предоставляет функции по загрузке изображений, работе с камерами и т.п., поэтому потребуется произвести эти действия какими-то сторонними средствами. Например, с помощью OpenCV.
Есть два способа использовать OpenCV в программах на C#. Первый способ – использовать существующие проекты, но они могут быть не самым лучшим выбором в силу лицензии, платности, заброшенности. Другой способ – объявить необходимые функции (и структуры) самостоятельно. Именно второй способ использован в программе iANPRcapture_motion_CShrp.
Объявленные функции и структуры можно увидеть в файле opencvDeps.cs, который входит состав программы.

4 О коде

В начале программа фильтрует входные данные и пытается загрузить лицензионный ключ (код опущен).
Заполняется структура с настройками для фунции распознавания и детектирования
ANPR_OPTIONS a = new 
a.Detect_Mode = 0x02 | 0x04 | 0x08; // == ANPR_DETECTCOMPLEXMODE
a.min_plate_size = 500;
a.max_plate_size = 50000;
a.max_text_size = 20;
a.type_number = Convert.ToInt32(args[1]);
a.flags = 0;


Затем в цикле загружается очередной кадр и один раз инициализируется iANPRCapture: создаётся объект с помощью CreateiANPRCapture, выделяется память с помощью CreateiANPRCapture, создаются две граничные линии, пересечение которых будет детектироваться (функцией CreateLineIntersection).

for (int i = 0; ; i++)
            {
                IntPtr frame = IntPtr.Zero;
                frame = OCV.cvQueryFrame(capture);
                if (frame == IntPtr.Zero)
                    break;

                if (grayFrame == IntPtr.Zero)
                {
                    grayFrame = OCV.cvCreateImage(OCV.cvGetSize(frame), 8, 1);

                    OCV.cvZero(grayFrame);
                    ((IplImage*)grayFrame)->origin = ((IplImage*)frame)->origin;			
                    i_capture = CreateiANPRCapture(20, a, OCV.cvRect(0, 0, ((IplImage*)frame)->width, ((IplImage*)frame)->height));
                    CreateMemoryForiANPRCapture(i_capture, 10, 15, 100);
                    Lines[0].x = (int)(((IplImage*)frame)->width * 0.1f); Lines[0].y = (int)(((IplImage*)frame)->height * 0.6f);
                    Lines[1].x = (int)(((IplImage*)frame)->width * 0.3f); Lines[1].y = (int)(((IplImage*)frame)->height * 0.6f);
                    Lines[2].x = (int)(((IplImage*)frame)->width * 0.1f); Lines[2].y = (int)(((IplImage*)frame)->height * 0.7f);
                    Lines[3].x = (int)(((IplImage*)frame)->width * 0.3f); Lines[3].y = (int)(((IplImage*)frame)->height * 0.7f);
                    CreateLineIntersection(i_capture, Lines[0], Lines[1], Lines[2], Lines[3]);
                }

                OCV.cvCvtColor(frame, grayFrame, 6);		 // 6 == CV_BGR2GRAY


Далее в том же цикле выделяется память под двумерный массив для хранения результата распознавания.
int tick1 = Environment.TickCount;		
                int i1 = -1000;
                CvRect[] Rects = new CvRect[100];
                int all = 100;
                IntPtr res = Marshal.AllocHGlobal(all * IntPtr.Size);

                for (int ii = 0; ii < all; ii++)
                {
                    Marshal.WriteIntPtr(res, ii * sizeof (IntPtr), Marshal.AllocHGlobal(21));
                    IntPtr subString = (IntPtr) Marshal.PtrToStructure(res + ii * IntPtr.Size, typeof (IntPtr));
                    Marshal.WriteByte(subString, 20, 0);
                }


И очередной кадр передаётся функции распознавания AddFrameToiANPRCapture.
if (a.type_number == 4 || a.type_number == 7)
                    i1 = AddFrameToiANPRCapture(i_capture, frame, ref all, Rects, res);
                else
                    i1 = AddFrameToiANPRCapture(i_capture, grayFrame, ref all, Rects, res);

                int tick2 = Environment.TickCount;
                float processingTime = (float)(tick2 - tick1) / 1000;
                Console.Write("Ret:{0}; num:{1}; time:{2:F3}; cand:{3}  ", i1, i, processingTime, all);

Затем результат распознавания выводится в консоль, на экран, и в файл «out.avi»
if (i1 == 0)
                {
                    for (int j = 0; j < all; j++)
                    {
                        IntPtr subStrPtr = (IntPtr)Marshal.PtrToStructure(res + j * IntPtr.Size, typeof(IntPtr));
                        string carNumber = Marshal.PtrToStringAnsi(subStrPtr);

                        if (carNumber.Length >= 1)
                        {
                            OCV.cvRectangle(frame, OCV.cvPoint(Rects[j].x, Rects[j].y), OCV.cvPoint(Rects[j].x + Rects[j].width,
                                Rects[j].y + Rects[j].height), OCV.CV_RGB(255, 255, 0), 2);

                            ...

                            OCV.cvPutText(frame, carNumber, pp1, ref font, OCV.CV_RGB(0, 0, 0));
                            OCV.cvPutText(frame, carNumber, pp2, ref font, OCV.CV_RGB(0, 255, 0));
                            Console.Write(carNumber + "; ");
                        }
                    }
                }//if ( i1 == 0 )			            
                Console.WriteLine();

                all = 100;
                CvPoint[] Points = new CvPoint[1000];
                int all_points = 1000;
                GetNumbersInMemory(i_capture, ref all, res, 20, Points, ref all_points);
                if (all > 0)
                {
                    for (int j = 0; j < all; j++)
                    {
                        IntPtr subStrPtr = (IntPtr)Marshal.PtrToStructure(res + j * IntPtr.Size, typeof(IntPtr));                        
                        string carNumber = Marshal.PtrToStringAnsi(subStrPtr);
                        Console.Write("({0})", carNumber);
                    }
                    Console.WriteLine();
                }

                ...

                if (cvVideoWriter == IntPtr.Zero)
                {
                    cvVideoWriter = OCV.cvCreateVideoWriter("out.avi", OCV.CV_FOURCC(Convert.ToSByte ('D'), Convert.ToSByte ('I'),
                        Convert.ToSByte ('V'), Convert.ToSByte ('3')), 20, OCV.cvGetSize(frame));
                }
                OCV.cvWriteFrame(cvVideoWriter, frame);

                // Рисование траектории и вывод дополнительного окна
                if (all > 0 && all_points > 1)
                {
                    for (int j = 0; j < all_points; j++)
                    {
                        OCV.cvCircle(frame, Points[j], 5, OCV.CV_RGB(0, 0, 255), 3);
                        if (j > 0) 
                            OCV.cvLine(frame, Points[j], Points[j - 1], OCV.CV_RGB(0, 0, 255), 2);
                    }

                    ...

                    OCV.cvShowImage("frame", image);
                    for(int j = 0; j < 60; j++ )
                        OCV.cvWriteFrame( cvVideoWriter, frame );
                }

                for (int j = 0; j < 100; j++)
                {
                    IntPtr subString = (IntPtr)Marshal.PtrToStructure(res + j * IntPtr.Size, typeof(IntPtr));
                    Marshal.FreeHGlobal(subString);
                }
                Marshal.FreeHGlobal(res);

                int c = OCV.cvWaitKey(20);
                if (c == 32)
                    c = OCV.cvWaitKey(0); // pause
                if (c == 27) break;
            }


Наконец, освобождается выделенная память
if (i_capture != IntPtr.Zero) 
                ReleaseiANPRCapture(ref i_capture);

            OCV.cvReleaseCapture(ref capture);
            OCV.cvReleaseImage(ref grayFrame);
            OCV.cvReleaseImage(ref image);

            OCV.cvReleaseVideoWriter(ref cvVideoWriter );
            return 0;
  • 0
  • 11 августа 2015, 21:18
  • DmitryK

Комментарии (0)

RSS свернуть / развернуть

Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.