
OneOCR — это набор из двух динамических библиотек и одной модели ONNX для распознавания текста в приложениях Snipping Tool и Photos в Windows 11.
Скажу сразу: статьи писать я не умею, а воды лить не хочу, поэтому писанины будет немного.
Итак, набор из трёх файлов состоит из: oneocr.dll, onnxruntime.dll и oneocr.onemodel.
Microsoft официально не предоставляет документацию и API к библиотеке OneOCR. Первое упоминание о OneOCR встречается у пользователя github b1tg. Все остальные найденные источники ссылаются на него.
Не знаю точно в какой версии Windows появилась эта функция, но в 23H2 у Snipping Tool 11.2409.25.0 она уже была. Также эти библиотеки и ONNX модель есть и у приложения Photos.
На момент написания статьи у меня в Windows 11 Pro 24H2 (26100.8328) установлены версии Photos 2026.11020.20001.0 и Snipping Tool 11.2601.12.0. При чём версия onnxruntime.dll у Snipping Tool старее (1.19.0.0) чем в Photos (1.23.0.0). Модели также различаются по содержимомоу.
Для примера я буду использовать ту, что посвежее. Замечу, что OneOCR хорошо работает на CPU (даже на моём стареньком Intel Core i3-4170), хотя, судя по релизам onnxruntime и экспортируемой из oneocr.dll функции OcrProcessOptionsSetRunBackendModelOnCPU, может работать и на GPU, но, скажу сразу, задействовать GPU мне не удалось.
Для минимального примера понадобится импортировать всего 5 функций:
result_t CreateOcrPipeline( const char *, const char *, init_options_t, pipeline_t * ); result_t RunOcrPipeline( pipeline_t, const image_t *, process_options_t, instance_t * ); result_t GetOcrLineCount( instance_t, uint64_t * ); result_t GetOcrLine( instance_t, uint64_t, line_desc_t * ); result_t GetOcrLineContent( line_desc_t, const char ** );
result_t — просто 32-битный целочисленный тип, хранящий код ошибки (всего их может быть 8, значения от 0 до 7 включительно).
Известные мне коды ошибок:
0 - результат успешного выполнения
2 - тип изображения находится за пределами диапазона [0;3]
3 - размер изображения превышает 10000 по ширине или высоте
4 - неправильный аргумент
init_options_t, process_options_t, pipeline_t, instance_t и line_desc_t — это указатели на внутренние структуры. Они «непрозрачны» и их содержимое по большому счету неизвестно, да нам оно и не особо-то и интересно, на самом деле.
image_t — структура, хранящая информацию об изображении. Информация о двух полях в этой структуре пока остаётся загадкой, но известно, какие значения они могут принимать и для примера этого хватит. Эксперименты показывают, что изображение должно быть в формате 32bpp (4 байта на пиксель). Причём порядок каналов RGB, как я понял, значения не имеет (ведь какая для ML-модели разница, какой будет текст, синий или красный?).
Простейший пример для лучшего понимания (я намеренно убрал все проверки на ошибки и код освобождения ресурсов):
#define WIN32_LEAN_AND_MEAN #include <windows.h> #include <wincodec.h> #include <cstdio> #include <cstdint> struct image_t { uint32_t type; // 0, 1, 2, or 3 uint32_t width; uint32_t height; uint32_t reserved; // ??? uint64_t stride; uint8_t * data; }; // struct image_t using result_t = uint32_t; using init_options_t = void *; using process_options_t = void *; using pipeline_t = void *; using instance_t = void *; using line_desc_t = void *; result_t (__cdecl *CreateOcrPipeline)( const char *, const char *, init_options_t, pipeline_t * ); result_t (__cdecl *RunOcrPipeline)( pipeline_t, const image_t *, process_options_t, instance_t * ); result_t (__cdecl *GetOcrLineCount)( instance_t, uint64_t * ); result_t (__cdecl *GetOcrLine)( instance_t, uint64_t, line_desc_t * ); result_t (__cdecl *GetOcrLineContent)( line_desc_t, const char ** ); void (__cdecl *ReleaseOcrPipeline)( pipeline_t ); void load_image( const wchar_t * filename, image_t * p_image ) { IWICImagingFactory * p_Factory = NULL; IWICBitmapDecoder * p_Decoder = NULL; IWICBitmapFrameDecode * p_Frame = NULL; IWICBitmapSource * p_BitmapSource = NULL; IWICFormatConverter * p_Converter = NULL; IWICBitmapSource * p_Result = NULL; CoInitialize( NULL ); CoCreateInstance( CLSID_WICImagingFactory, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS( &p_Factory ) ); p_Factory->CreateDecoderFromFilename( filename, NULL, GENERIC_READ, WICDecodeMetadataCacheOnDemand, &p_Decoder ); p_Decoder->GetFrame( 0, &p_Frame ); p_Frame->QueryInterface( IID_IWICBitmapSource, reinterpret_cast<void **>(&p_BitmapSource) ); p_Frame->GetSize( &p_image->width, &p_image->height ); p_image->stride = p_image->width * 4; p_image->data = new uint8_t [p_image->stride * p_image->height]; WICPixelFormatGUID pixel_format; p_Frame->GetPixelFormat( &pixel_format ); // Convert format to GUID_WICPixelFormat32bppRGB... if ( pixel_format != GUID_WICPixelFormat32bppRGB ) { p_Factory->CreateFormatConverter( &p_Converter ); p_Converter->Initialize( p_BitmapSource, GUID_WICPixelFormat32bppBGR, WICBitmapDitherTypeNone, NULL, 0.f, WICBitmapPaletteTypeCustom ); p_Converter->QueryInterface( IID_PPV_ARGS( &p_Result ) ); } p_Result->CopyPixels( NULL, p_image->stride, p_image->stride * p_image->height, reinterpret_cast<BYTE *>( p_image->data ) ); } int main( int argc, char * args[] ) { SetConsoleOutputCP( CP_UTF8 ); if ( argc != 2 ) { printf( "Use: %s image_filename\n", args[0] ); return 1; } HMODULE dll = LoadLibrary( "oneocr.dll" ); CreateOcrPipeline = (decltype( CreateOcrPipeline ))GetProcAddress( dll, "CreateOcrPipeline" ); RunOcrPipeline = (decltype( RunOcrPipeline ))GetProcAddress( dll, "RunOcrPipeline" ); GetOcrLineCount = (decltype( GetOcrLineCount ))GetProcAddress( dll, "GetOcrLineCount" ); GetOcrLine = (decltype( GetOcrLine ))GetProcAddress( dll, "GetOcrLine" ); GetOcrLineContent = (decltype( GetOcrLineContent ))GetProcAddress( dll, "GetOcrLineContent" ); // Convert image path to wide-char for image loader... wchar_t w_filename[MAX_PATH] = {}; swprintf( w_filename, MAX_PATH, L"%s", args[1] ); image_t image = { 3/*type*/ }; load_image( w_filename, &image ); pipeline_t pipeline = nullptr; CreateOcrPipeline( "oneocr.onemodel", "kj)TGtrK>f]b[Piow.gU+nC@s\"\"\"\"\"\"4", nullptr/*init options*/, &pipeline ); instance_t instance = nullptr; RunOcrPipeline( pipeline, &image, nullptr/*process options*/, &instance ); uint64_t n_lines = 0; GetOcrLineCount( instance, &n_lines ); for ( int i = 0; i < n_lines; i++ ) { // Get line descriptor... line_desc_t line_desc = nullptr; GetOcrLine( instance, i, &line_desc ); if ( line_desc ) { // Get null-terminated line ptr... const char * line_ptr = nullptr; GetOcrLineContent( line_desc, &line_ptr ); printf( " %s\n", line_ptr ); } } return 0; }
Второй параметр функции CreateOcrPipeline - это ключ для распаковки ONNX модели. Во всех версиях что я пробовал он всегда был один и тот же.
Код элементарный, комментировать тут особо нечего.
Пара примеров распознавания рукописного текста (я человек простой, поэтому примеры изображений стащил из чужой статьи 2025-го года):
Скрытый текст

Привет. Это образен для распознавания ТЕкстА ДЛя сТАТьИ в Т-Ж. ПровЕрим сколько слов он определит полностью, > сколько прЕврАтит в нАБор Букв и сколько вообш,Е нЕ узнаЕт: 1. Набор слов для распознавания №1. 2. Ещё один набор слов. 3. Третий набор слово. 4. Экспрессия-четвертое слово. 5. Финальное пятое слово.
Скрытый текст

Я вас любил: любовь ещё, быть может В душе моей учасла не совсем; пусть она вас большше не тревожит; Я не хочу печалить вас ничим. Я вас любии безлитивно, безна дежно, По рабостью, то равностью тамим; Я вас любил этал искренно, ток нежно, Как дай вам Бы любимой быть другим.
Ну и один пример распознавания печатного текста (изображение было взято где-то в сети интернет):
Скрытый текст

В богатовских садах Рассказ М.С.Богатовец Начиная, от Панфилихи по обрывистому берегу Северного Донца в протяжении двух километров тянутся богатовские сады. Это одно из бо- гатых мест хутора. Оно всегда привлекало на себя внимание не только народа, но и перелетную птицу, полевых зверей и охотников на чужое добро. В летнее время по садам кочуют косяками дворовые подростки. Они охотятся на сорочиные яйца, касачек, сизокрылых шнурков. Купают- ся в Донце и исподволь пасутся по чужому крыжовнику. Свежий ветерок из Донца покачивает богатые деревья фруктами: яблоками, сливами, гру- шами и обильной вишней. *** В истории прошлого, участки садов передавались из рук в руки по на- следству. Местные жители хорошо возделывают землю под садами. На зиму или весной земля вскапывается. Нижние части плодородных ство- лов наносятся известью, для борьбы с вредителями. А сухие деревья уничтожаются и используются для топлива. Стены садов слегка ремонти- руются, как ограждение от бродящего скота. Сады всячески охраняются, особенно в весеннее время. Осенью, ког- да все с садов убрано, в ночное время некоторые жители позволяют себе и запускают скот для откормки. Однако, это в ущерб садам, и в этот пери- од некоторые более трудолюбивые хозяева усиливают ограждение. Над- сматривают над своим салом и порыкивают на свиней и скотину. *** В особенности привлекательны сады в весеннее время, когда зелень в полном своем расцвете. С садов доносится в хутор несмолкаемый пти- чий гам. Поют, где-то в поднебесьях соловьи. Пророчат вечно перелетные кукушки. Пыхтят на каменных стенах пастушки со своим острым гребнем. Летают и пересаживаются из стены на стену чикалки, помахивая сво- им сереньким хвостом. Пролетают над головами, на большой скорости скворцы. Они направляют свой путь в колки, где им привольно и обилье корма. Воркуют горлинки по своим детям. Эта одна из бедных несушек в наших садах, которая не в состоянии даже сделать, как следует себе гнездышко. Пролетают над садами дикие утки. Их полет напоминает ле- тевшую со свистом бомбу. Где-то далеко в садах назойливо поет иволга. Летят сороки по направлению хутора на охоту на цыплят. Ее небольшие крылья стягивают к себе воздух. Сзади широкий разлатый хвост, регули- рует свое направление. Черные галки сидят на вершинах тополей и ос- матривают окружение. По Дону, шлепая полостями, поднимается в гору пароход с баржами, и выпущенным черным дымом покрывает сады. *** Пестреющий в садах народ, как и птица, каждый занят своим делом. Одни полют высоко поднявшуюся траву. Другие, закутав нос платком от назойливых комаров, рвут вишню завтра к базару. Третьи чинят изгородь, рвут калюку и накладывают на стену. Четвертые, расположившись под
Пример выше и более правильная реализация (с проверками ошибок и т. п.) находятся на GitHub
Тут описаны не все её возможности. Из неописанных есть функции:
определения угла поворота изображения
получения отдельных слов
получения Bounding Box строк и слов
получения значения
confidence(достоверности) отдельных слов
