You built OpenCV 4.6.0 from source with CMake, set the include and library directories in Visual Studio 2022, pointed Additional Dependencies at opencv_world460d.lib, matched the runtime with /MTd — and the build answers with hundreds of LNK2019 and LNK2001 errors, the first ones naming opj_image_destroy. Nothing is wrong with your build of OpenCV and nothing is wrong with your paths. The world static library is one input short of a complete set: the image codecs it calls were built beside it and never went on the link line. This page decodes the symbols, names the libraries and where they live, walks the fix for Debug and Release separately, and ends with the deployment case for running the result as one static .exe on a shop-floor vision controller.
Why does a static OpenCV build leave unresolved external symbols?
Because opencv_world460.lib is a client of the codec libraries rather than their container: it calls OpenJPEG, libjpeg-turbo, libpng, libtiff, libwebp and zlib, which remain separate static .lib files, so a link line that names only the world lib leaves every codec symbol unresolved.
LNK2019 is the linker reporting a call to a symbol with no definition among its inputs; LNK2001 repeats the report per object file. OpenCV's imgcodecs module — the code whose loadsave.obj reads and writes image files — calls the OpenJPEG API through cv::detail::OpjImageDeleter, a small wrapper that owns an opj_image handle and frees it with opj_image_destroy. Those function bodies are not inside opencv_world460d.lib; OpenJPEG was built as its own static library beside it. With the DLL build, opencv_world460.dll already carries the codecs, which is why the same application links and runs the moment you switch to shared libraries. With the static build, every callee must be listed by hand, and the linker files one report per referencing object — dozens or hundreds of lines for what is really one missing folder. The mechanics are the same for camera SDKs; the C++ undefined reference guide unpacks them in general.
How do I tell which library a missing symbol belongs to?
Read the prefix before the first underscore: opj_ is OpenJPEG, jpeg_ or tj is libjpeg-turbo, png_ is libpng, TIFF is libtiff, WebP is libwebp, and inflate, deflate or crc32 is zlib — one prefix per missing library, and each prefix in your error list is one lib you must add.
OpenCV calls every codec through that codec's own C API, and those C names keep their prefix even though the C++ caller around them is name-mangled. Work down the table and each block of errors collapses to one input library.
| Prefix in the error | Library to add | Example symbol |
|---|---|---|
| opj_ | libopenjp2 (JPEG 2000) | opj_image_destroy |
| jpeg_, tj | libjpeg-turbo | jpeg_read_header |
| png_ | libpng | png_create_read_struct |
| TIFF | libtiff | TIFFOpen |
| WebP | libwebp | WebPGetInfo |
| inflate, deflate, crc32 | zlib | inflateInit_ |
| quirc_ | quirc (QR codes) | quirc_decode |
The error line also names the object holding the caller — here opencv_world460d.lib(loadsave.obj), with a second report from grfmt_jpeg2000_openjpeg.obj — which confirms the caller lives in the world lib while the callee does not.
Which 3rd-party libraries do I add, and where are they?
Add every .lib in the static-lib output except opencv_world itself — typically eight libraries: zlib, libjpeg-turbo, libpng, libtiff, libwebp, libopenjp2, quirc and the bundled ippicvmt — from the install's x64\vc17\staticlib folder, or from 3rdparty\lib in the build tree if the install did not copy them.
A default Windows build with the world option produces those eight beside the world lib. If you switched a codec off in CMake, your folder holds fewer, and the folder — not any list, including this one — is the source of truth.
Worked example. The install folder here holds exactly two .lib files in x64\vc17\staticlib: opencv_world460d.lib for Debug and opencv_world460.lib for Release. A complete static set for one configuration is 1 world lib + 8 codec libs = 9 inputs, so each configuration listed 1 of 9 and left 8 library families unresolved. Those 8 sit in the build tree under 3rdparty\lib — search the build folder for *.lib and they appear. Eight missing families, each printing its own LNK2019 block plus a LNK2001 per referencing object, is exactly how one folder becomes hundreds of error lines.
If the build or install never produced the codecs at all, the fault is one stage earlier, at generate or install time; the CMake OpenCV submodule find_package fix covers that stage.
How do I add them to the right Debug and Release configurations?
Edit the two configurations separately: with the property-page configuration selector on Debug, append the d-suffixed codec set to Linker → Input → Additional Dependencies, then switch the selector to Release and append the unsuffixed set — the d suffix is the only difference between the two lines.
- Confirm the active configuration in Build → Configuration Manager, then open Project → Properties with Configuration set to Debug and Platform to x64; the errors you are fixing belong to whichever configuration just failed.
- In Linker → Input → Additional Dependencies, append opencv_world460d.lib plus every d-suffixed codec lib from staticlib or 3rdparty\lib, semicolon-separated. Keep C/C++ → Code Generation at /MTd so your runtime matches the static-CRT OpenCV build.
- Switch Configuration to Release and repeat with opencv_world460.lib and the unsuffixed set, runtime /MT.
- Do not edit the list under All Configurations: the d suffix differs, so a shared list is wrong for one of the two by construction.
- Run Build → Rebuild Solution so no object from the failed attempts survives; the error list should drop to zero.
- If any error survives, decode its prefix in the table above, add that library, and rebuild once more.
What else causes LNK2019 with a static OpenCV build?
Three other mismatches produce the same error code: a runtime mismatch shows LNK2038 with RuntimeLibrary values such as MTd_StaticDebug versus MDd_DynamicDebug, a platform mismatch shows LNK1112 with conflicting machine types, and a static library built before Visual Studio 2015 can leave UCRT symbols unresolved.
The runtime case first. Your OpenCV static build links the static CRT when BUILD_WITH_STATIC_CRT is on, which is why /MTd and /MT belong in the recipe; an application that stays at /MD fails with LNK2038, mismatch detected for RuntimeLibrary, before any OpenCV symbol resolves. Either match the runtime — as the Debug /MTd and Release /MT settings above do — or rebuild OpenCV with that option off.
Platform is next: an x86 project against x64 libraries reports LNK1112, machine type conflict, not LNK2019; keep both sides x64, and if you must produce both, the 32-bit and 64-bit side-by-side install guide keeps the two library sets apart on one machine.
The old-toolset case bites old vendor SDKs, not a fresh OpenCV build: a static library produced before the Visual Studio 2015 toolset change can leave UCRT symbols unresolved against a current project, and rebuilding that library with your toolset ends it.
Two neighbouring errors mean something different. LNK1104, cannot open file, is a wrong name or path — the OpenCV LNK1104 fix sorts that one out. And the pattern is not OpenCV-specific: acquisition SDKs fail with the same code for the same reason, a missing input library, as resolving NI-IMAQ LNK2019 unresolved external symbol errors walks through for one image-acquisition SDK.
Does the same failure hit module-by-module static builds?
Yes: with the world build turned off you link opencv_core460.lib plus one lib per module you use, and the same codec set still applies — a static highgui build hits the identical wall after a version upgrade when the codec libs drop off the input line.
On Windows the module files are opencv_core460.lib, opencv_imgproc460.lib, opencv_imgcodecs460.lib and so on, and a console vision application typically needs core, imgproc, imgcodecs and highgui. The codecs live in imgcodecs — which is why the world lib's loadsave.obj carries the codec calls — and highgui pulls imgcodecs in for imwrite and imshow. Whatever the packaging, the 3rdparty set is identical, so the fix is the same list. Building with BUILD_LIST=core,imgproc,imgcodecs,highgui keeps the module set, and the input list, small. An upgrade that swaps libraries without re-checking the input list reproduces the fault on any OS; the library upgrade build-error checklist is the generic walkthrough.
When is static OpenCV worth it on a vision controller?
Static linking earns its keep on a deployed vision controller, where one self-contained .exe means no OpenCV DLLs to copy, no redistributable to install and no version to match; the cost is size, a few to a few tens of megabytes per executable.
The size number is honest arithmetic, not a warning: the linker pulls only the objects your code references, so a reader touching core, imgproc and imgcodecs grows by a few megabytes, while an application that also uses the DNN module grows by tens. The finished .exe then runs on any machine with no OpenCV installed — which is precisely what a line-side controller is. Licensing supports the choice: OpenCV 4.6.0 ships under Apache 2.0, which permits static linking, and the bundled codecs are permissively licensed; keep their notice files with the shipped application.
When that .exe's home is a controller on the line rather than a desk tower, one-file deployment is the point. The vision controllers category is full of PC-class machines sized for exactly this job, from Intel Core i3-class units upward.
- LAN ports
- 5+ LAN ports
- USB 3 ports
- 3-4 USB 3 ports
- LAN ports
- 3-4 LAN ports
- USB 3 ports
- 2 USB 3 ports
- LAN ports
- 5+ LAN ports
- USB 3 ports
- 3-4 USB 3 ports
How do I verify the build is really static?
A clean rebuild with zero linker errors is not yet proof: run the .exe on a machine with no OpenCV installed, and run dumpbin /dependents on it — the image must display and the import list must not contain opencv_world460.dll or any other OpenCV DLL.
The load test is the deployment proof: the same .exe that shows an image on a bare machine will run on the controller, because nothing is imported from an OpenCV DLL. dumpbin /dependents yourapp.exe prints the import table, and any opencv_world*.dll line there means a stray import library crept back onto the input line. The error arithmetic is the build proof: before the fix, eight missing library families printed an LNK2019 plus an LNK2001 per referencing object — hundreds of lines; after the complete list, a clean rebuild prints zero.
Prevention is one habit: pin the OpenCV build folder per project, record its CMake options beside it, and carry both property-page input lines in the .props sheet so the next engineer starts from the working set. For everything else that fails between CMake and a linked vision application, the systems topic page collects the build-stage fixes.
The decision, in one line: static OpenCV in Visual Studio 2022 means the world lib plus every codec lib in the same folder, listed per configuration, with the runtime matched at /MTd and /MT — nothing more and nothing less. If the runtime target is a controller on the line, pick it from the vision controllers above and deploy a single .exe; if a build still fights you, the contact page reaches the applications team.
Questions
Does the order of the .lib files matter to the Microsoft linker?
No. LINK.EXE resolves symbols from all inputs regardless of order, so opencv_world first or last both link. That differs from GNU ld on Linux, where static archives are scanned once in order and a wrong order leaves undefined references. List all libraries, match the d suffix to your configuration, and the order you type them in is irrelevant.
Can I add only the one library the first error points to?
You can, and the next build will fail on the next library's symbols. Each missing codec costs a full rebuild cycle of a few minutes, so a folder-wide add is faster: every .lib in staticlib or 3rdparty\lib goes on the line once, and no later codec error can appear. Unused members are not pulled into the .exe, so extra entries cost nothing at runtime.
Why does a CMake project link OpenCV statically without these errors?
The exported OpenCVConfig.cmake marks the codec libraries as interface link dependencies of the world target, so find_package(OpenCV) puts every 3rdparty lib on the link line automatically. The failures happen in hand-built Visual Studio property pages, where nobody carries that dependency list for you. Matching the CMake generator and toolset to your project keeps the two builds consistent.
Why does a build I never use JPEG 2000 in still want opj_image_destroy?
OpenCV's image loader registers every codec that was compiled in, so the imgcodecs code inside opencv_world references the OpenJPEG functions whether or not a .jp2 file ever reaches your application. Turning the codec off at build time with WITH_OPENJPEG=OFF, or limiting modules with BUILD_LIST, removes those symbols and shrinks the library list you must carry.
What do I have to ship with a dynamically linked OpenCV build instead?
The import library resolves symbols at build time, but the .exe then loads opencv_world460.dll at run time, so the matching DLL — and the Visual C++ redistributable for the toolset that built it — must be present on every target machine. That is the deployment work a static build removes: one self-contained .exe instead of a DLL, its runtime and version discipline.


