
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 — это успешный результат).
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_ttype;// 0, 1, 2, or 3uint32_twidth;uint32_theight;uint32_treserved;// ???uint64_tstride;uint8_t *data;}; // struct image_tusing 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;}HMODULEdll = 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_tw_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;}
Код элементарный, комментировать тут особо нечего.
Пара примеров распознавания рукописного текста (я человек простой, поэтому примеры изображений стащил из чужой статьи 2025-го года):
Скрытый текст

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

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

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