LINUX.ORG.RU

qt ли c#, что быстрее?

 , ,


1

2

Как-то писал прогу, которая распознает с экрана графические примитивы (кружки, треугольники, квадраты и т.д.). Алгоритм примерно такой:

1. Получил доступ к десктопу  QDesktopWidget * desk = QApplication::desktop();
    и к скрину QScreen *screen = QGuiApplication::primaryScreen();
2. Грабанул кусок экрана в pixmap 
    boardPixmap = screen->grabWindow(desk->winId(), boardX, boardY, boardW, boardH);
3. Преобразовал pixmap в QImage  boardImage = boardPixmap.toImage()
4. Картинка логически организована как поле 20х20 клеток
4. Используя QImage::pixelColor() прохожу по диагонали каждую клетку и попиксельно считываю цвета каждого пикселя и делаю своего рода хэш примитива. Сравнивая с эталонным хэшем определяю, что за примитив в клетке.

Это всё было под линуксом. Теперь хочу это реализовать в винде. Не могу определиться, заюзать qt или может в c# есть возможность реализовать задачу, чтобы работало быстрее, чем в qt? Т.е. можно ли в c# сделать быстрее попиксельное чтение картинки?

Понятно, что тут больше qt юзают, но может и c# кто знает неплохо?

★★★★

Звучит как задача для OpenCV.

QImage::pixelColor()

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

stabilitron
()
Ответ на: комментарий от cocucka_B_TECTE

Хотел как проще объяснить, получилось не очень. Изложу подробней:

По факту мои примитивы не стандартные геометрические фигуры (треугольник, квадрат, ромб и т.д.). Примитивы могут быть произвольной формы, их 20 разновидностей, при чем они при разных запусках программы могут менять свою форму. Примитив - это рисунок черным цветом на белом фоне.

Сначала я калибрую эти примитивы (составляю цифровой хэш примитива, каждый бит (1/0) в хэше - это пиксель (черный/белый) на картинке). Прохожу по диагонали картинку примитива и каждый пиксель диагонали запихиваю через сдвиг влево в хэш). И вот таким образом я формирую базу примитивов. Эта база создается при каждом запуске программы заново, т.к. примитивы могут отличаться от запуска к запуску.

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

Может ли этот OpenCV работать с примитивами произвольной формы? Которые ещё и обновляются при каждом запуске. И насколько быстро это будет?

Тот способ, который я применяю меня вполне устраивает, но я не знаю, как соотносится скорость обработки в qt и в c#. Собственно об этом и вопрос

Сейчас поле 20х20 клеток каждая 10х10 пикс) с 20 примитивами обрабатывается за 0,05-0,10 сек.

Мне нужно что-то что работало бы под виндой не медленнее чем сейчас

Chord ★★★★
() автор топика
Последнее исправление: Chord (всего исправлений: 1)
Ответ на: комментарий от unDEFER

Быстрее будет как сказали выше работать с буфером напрямую.

В qt меня скорость устраивает, но хотелось бы конечно быстрее.

Работать в винде с буфером - это вспоминать winapi и через это получать буфер. Простой GetPixel конечно будет очень медленно.

А в c# нет такого способа, чтобы получить доступ к экранному буферу?

Chord ★★★★
() автор топика
Последнее исправление: Chord (всего исправлений: 1)
Ответ на: комментарий от Chord

Работать в винде с буфером - это вспоминать winapi и через это получать буфер.

Нет, вы не поняли. И в Qt и где угодно есть функция чтобы получить доступ именно к буферу. В QImage это например BytesPerLine.

unDEFER ★★★★★
()

в любом случае, первым шагом алгоритма будет скопировать пиксели из экрана в буфер.

в qt это делает, как ты уже выяснил, grabwindow().toimage(), в winapi – bitblt(wnd, dc)[1].

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

в qt это qimage.bits()/scanline/bytesperline

в winapi это getobject(hbitmap,&bitmap);bitmap->bmbits

в .net это тоже всё наверное как-нибудь делается, посмотри исходник любой скриншотилки.

[1] https://learn.microsoft.com/en-us/windows/win32/gdi/capturing-an-image

anonymous
()
Ответ на: комментарий от Chord

поле 20х20 клеток каждая 10х10 пикс) с 20 примитивами

То есть один примитив — 10x10 пикселов? Градаций серого нет, строго чёрные или белые пикселы?

20 разновидностей, при чем они при разных запусках программы могут менять свою форму

Если форма и положение примитива не меняется в течение одного запуска, то можно просто сравнивать с шаблоном или вычислять разницу и по порогу уже с какой-то вероятностью относить изображение к том или иному примитиву. Тейк про диагональный хеш, если честно, я не понял — зачем оно надо?

Если при каждом запуске форма примитивов меняется, то, получается, ты сперва составляешь полный набор, с которым впоследствии сравниваешь?

Возможно задача действительно тривиальная и достаточно просто поискать по словарю, скажем, 128-битное значение.

stabilitron
()
Ответ на: комментарий от stabilitron

То есть один примитив — 10x10 пикселов? Градаций серого нет, строго чёрные или белые пикселы?

да, один примитив 10х10, градации серого могут быть на границах примитива из-за сглаживания

Если при каждом запуске форма примитивов меняется, то, получается, ты сперва составляешь полный набор, с которым впоследствии сравниваешь?

именно так, это делается один раз, поэтому скорость составления некритична.

Если форма и положение примитива не меняется в течение одного запуска, то можно просто сравнивать с шаблоном

В течение одного запуска форма не меняется, но положение может меняться, поэтому надо просматривать всю картинку 20х20 клеток

Тейк про диагональный хеш, если честно, я не понял — зачем оно надо?

Сравнивать два хэша (uint) быстрее, чем попиксельно сравнивать картинки. И чем больше примитивов - тем более выгодно использовать целочисленное сравнение.

Если читать по горизонтали (или вертикали) к примеру одну линию в пределах клетки 10х10, например по центру клетки, то иногда возникают коллизии, два разных примитива могут давать одинаковый хэш. При чтении по диагонали коллизий нет, однозначно хэш определяет примитив.

В общем скачиваю виндовый qt, посмотрю, если скорость как в моём алгоритме устраивает - так и оставлю. Если будет медленнее, чем в линуксе - буду смотреть буфер

Chord ★★★★
() автор топика
Последнее исправление: Chord (всего исправлений: 1)

«Что лучше, пена или дом?..»

C# это язык, Qt это фреймворк, который можно вызывать из C++ либо из Питона по твоему выбору. Сравнивать ЯП с набором библиотек это какое-то новое слово в выборе средств разработки.

Если бы меня волновало быстродействие, я бы, конечно, взял плюсы. И даже не факт, что с кутями, хотя если тебе кроме распознавания ещё какое-то GUI нужно, то почему бы и нет.

Странно, что имея готовое решение на Qt, ты ищешь какого-то повышения быстродействия от интерпретируемого (пусть и с JIT-ом) C#, который за собой ещё и кучку рантайма тянет. С кутями ты вообще можешь всё в нативный статический бинарь слинковать, например, по моей инструкции (Windows + Qt5), есть и другие.

hobbit ★★★★★
()
Ответ на: комментарий от hobbit

Странно, что имея готовое решение на Qt, ты ищешь какого-то повышения быстродействия от интерпретируемого (пусть и с JIT-ом) C#, который за собой ещё и кучку рантайма тянет

Если скорость будет не хуже чем в линуксе, то меня это вполне устроит. И я просто пересоберу свой исходник под виндой, заменив чисто линуксовые моменты на виндовые (вызов внешних тулз)

Chord ★★★★
() автор топика

Сконвертруй QImage с помощью QImage::convertTo в формат с минимальным количеством битов на пиксель. Далее используй QImage::bits() для доступа к пикселям и проходу по ним. Это будет быстро. Конвертирование не очень. Чтобы увеличить скорость до предела, нужно изначально создавать картинки так, чтобы конвертировать было не нужно.

Как картинки вообще создаются на экране? В твоей программе? Или ты скриншотишь другое приложение?

rumgot ★★★★★
()
Ответ на: комментарий от Chord

положение может меняться, поэтому надо просматривать всю картинку 20х20 клеток

Я имел в виду положение внутри одной «клетки» 10x10. Если положение (но не масштаб) примитива меняется внутри клетки, то тогда, думаю, самым надёжным способом сравнения будет кросс-корреляция массивов пикселов 100x1.

однозначно хэш определяет примитив

Твой диагональный хеш, конечно, будет гораздо быстрее кросс-корреляции. Если сделаешь ещё через прямой доступ к буферу, то, думаю, в сотню-другую раз можно ускориться. С кросс-корреляцией и прямым доступом к буферу, думаю, скорость будет примерно того же порядка, что у тебя сейчас, а может и медленнее.

stabilitron
()
Ответ на: комментарий от Chord

Сначала я калибрую эти примитивы (составляю цифровой хэш примитива, каждый бит (1/0) в хэше - это пиксель (черный/белый) на картинке). Прохожу по диагонали картинку примитива и каждый пиксель диагонали запихиваю через сдвиг влево в хэш). И вот таким образом я формирую базу примитивов. Эта база создается при каждом запуске программы заново, т.к. примитивы могут отличаться от запуска к запуску.

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

Звучит как feature extraction с последующим feature detection. Всё это OpenCV прекрасно умеет.

https://docs.opencv.org/4.x/db/d27/tutorial_py_table_of_contents_feature2d.html

cocucka_B_TECTE
()

Можно использовать Windows.Forms:

        [DllImport("user32")]
        private static extern bool SetProcessDPIAware();

        public static unsafe void Main()
        {
            SetProcessDPIAware();
            var rc = Screen.AllScreens[0].Bounds;
            using var bitmap = new Bitmap(rc.Width, rc.Height, PixelFormat.Format32bppArgb);
            using (var graphics = Graphics.FromImage(bitmap))
            {
                graphics.CopyFromScreen(rc.Left, rc.Top, 0, 0, rc.Size);
            }

            var data = bitmap.LockBits(rc, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
            try
            {
                var ptr = (uint*)data.Scan0;
                var firstPixel = ptr[0];
                var a = firstPixel >> 24 & 0xFF;
                var r = firstPixel >> 16 & 0xFF;
                var g = firstPixel >> 8 & 0xFF;
                var b = firstPixel & 0xFF;
                Console.WriteLine("R={0} G={1} B={2} A={3}", r, g, b, a);
            }
            finally
            {
                bitmap.UnlockBits(data);
            }
        }

Работа с указателями и сырыми данными в битмапе будет не медленнее крестов, более того ты можешь SIMD типы из System.Numerics подключить при желании, итп.

lovesan ★★
()
Ответ на: комментарий от hobbit

MSIL и тем более C# не интерпретируется. MSIL всегда компилируется при старте, и компилируется в очень эффективный машкод. (ну и есть даже возможность полного AOT, когда прямо все сразу компилируется в машкоды, но там есть ограничения)

lovesan ★★
()
Последнее исправление: lovesan (всего исправлений: 1)
Ответ на: комментарий от Chord

Конечно есть, т.к. как минимум любое сишное и крестовое говно можно обернуть и дёргать из C#. Обратно вот сложнее. Ты лучше скажи как ты картинку дёргать хочешь? Варианты есть разные, вплоть до чтения памяти другого процесса который эту картинку показывает.

peregrine ★★★★★
()
Ответ на: комментарий от andreyu

Что быстрее, фреймворк или язык?

Подозреваю, что ТС под С# всё же имел ввиду .NET. Так что фреймворк и фреймворк. А по скорости, почти уверен, что Qt с плюсами будет быстрее. А OpenCL можно использовать и там и там. Может ещё вариант на GPU перенести расчеты, но это уже от алгоритмов используемых зависит и подозреваю, что довольно сложно будет.

Loki13 ★★★★★
()
Ответ на: комментарий от Loki13

Подозреваю, что ТС под С# всё же имел ввиду .NET

Не каждый может это понять...

Что касается самой проги - перекомпилял с минимальными изменениями под винду. Никаких проблем со скоростью работы нет. Все замечательно.

Как шикарный бонус - include <qt_windows.h> - и весь винапи в распоряжении. Не надо выворачиваться наизнанку, пытаясь сделать что-то сложнее обработки стандартных кнопок/текстовых полей. А когда-то вызов винапи из qt программы был реальным геморроем..

Chord ★★★★
() автор топика
Ответ на: комментарий от andreyu

Как здорово, что Локи подсказал вам, что вы имели в виду :)

Глаза протри и посмотри, кому он подсказал

Chord ★★★★
() автор топика
Последнее исправление: Chord (всего исправлений: 1)
Ответ на: комментарий от Chord

Как шикарный бонус - include <qt_windows.h> - и весь винапи в распоряжении.

Это не бонус, а капкан. Потом тебе понадобится актуализировать сборку под Linux, и ты будешь думать, чем этот винапи заменять (или просто убрать в #ifdef, ибо был притащен для украшательств).

Что именно ты не смог сделать кроссплатформенными средствами «сложнее обработки стандартных кнопок/текстовых полей»? Хотелось бы пример.

hobbit ★★★★★
()
Ответ на: комментарий от hobbit

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

Программно тыкать мышью за пределами окон своего приложения, читать клавиатурный ввод за пределами своего приложения. Для первого нужен костыль xdotool либо SendInput. Для второго - xinput либо глобальный хук на клавиатуру. Что из этих четырех реализовано в виде кросплатформенного решения?

Chord ★★★★
() автор топика
Последнее исправление: Chord (всего исправлений: 1)
Ответ на: комментарий от hobbit

Это не бонус, а капкан. Потом тебе понадобится актуализировать сборку под Linux, и ты будешь думать, чем этот винапи заменять (или просто убрать в #ifdef, ибо был притащен для украшательств).

Мне не надо это актуализировать.

Chord ★★★★
() автор топика