What does cv2.imread() return when it cannot read the file, and why?
answer
- it does not raise on failure
- the return value is the error signal
- the traceback appears much later, elsewhere
- 'NoneType' object has no attribute 'shape'
- check with `is None`, never truthiness
basics
~20 scv2.imread() returns None on any failure — missing path, unreadable permissions, corrupt data, or an unsupported format. It never raises. The error surfaces later, usually as 'NoneType' object has no attribute 'shape', so check for None immediately after the read.
solid answer
~50 s`cv2.imread` wraps a C++ function that returns an empty `Mat` on failure, and the Python bindings translate an empty `Mat` to `None`. There is no exception and no message, so a typo in a path, a relative path resolved against the wrong working directory, a file that is actually HTML from a failed download, or a format with no built-in codec all produce the same silent `None`. The failure then appears many lines later as `AttributeError: 'NoneType' object has no attribute 'shape'`, or as a cv2 error deep inside `cvtColor`, which points at the wrong line. The habit is to guard on the spot: `img = cv2.imread(path); if img is None: raise FileNotFoundError(path)`. Note that `if not img:` is wrong — NumPy raises on the truth value of a multi-element array — and `img.size` blows up on `None`.
code
python · 12 linesimport os
import cv2
def load_bgr(path):
img = cv2.imread(path, cv2.IMREAD_COLOR)
if img is None:
raise FileNotFoundError(f"cannot decode image: {os.path.abspath(path)}")
return img
# WRONG: raises ValueError on a healthy multi-element array
# if not img:
# ...go deeper
Remember the one fact and the one line of code: imread returns None instead of raising, so check if img is None: right after the call. Being able to recognise the 'NoneType has no attribute shape' traceback is the point of the question.
Explain why the failure surfaces far from its cause, and list the realistic causes — relative paths against the process working directory, truncated downloads, missing codecs. Mention that imdecode and imwrite follow the same value-returning contract.
Talk about failing loudly in a batch or service context: raise with the absolute path, distinguish missing from undecodable with an existence check, and make sure one bad file cannot silently shrink a batch's output.
Frame it as an API-contract issue at a system boundary — third-party I/O that signals failure by value needs a wrapper that normalises it into your error model, so that ingest failures are counted and alerted on rather than reappearing as mysterious NoneType errors in unrelated code.
## The behaviour In C++, `cv::imread` returns a `cv::Mat`, and on failure it returns an *empty* `Mat` rather than throwing. The Python binding converts an empty `Mat` to `None`. So `cv2.imread("typo.png")` quietly evaluates to `None`, and execution continues as if nothing went wrong. This is the single most common first-day OpenCV bug, and it is worth being precise about *why* it is confusing: the traceback you eventually see does not mention reading a file at all. Typical shapes of the delayed failure: - `AttributeError: 'NoneType' object has no attribute 'shape'` when you inspect dimensions. - A `cv2.error` from `cvtColor`, `resize` or `imshow` complaining about an empty input, several functions downstream. - Worse, in a loop over a directory, one unreadable file poisons only that iteration, so a batch job produces silently incomplete output. ## What causes it - **A wrong path.** The most common cause by far, and relative paths are the usual culprit: `imread("data/img.png")` resolves against the *process working directory*, not the script's directory. A notebook, a systemd unit and a test runner each set that differently. - **A path that exists but is not an image.** A truncated download, a Git LFS pointer file, or an HTML error page saved with a `.jpg` extension. The extension is irrelevant to the decode — OpenCV sniffs the content. - **No codec for the format.** OpenCV decodes by delegating to bundled or system codecs. Exotic or newly popular formats may not be built into the wheel you installed. - **Permissions**, or a path that is a directory. - **Non-ASCII paths on some platforms.** Historically a recurring problem on Windows. The portable workaround is to read the bytes yourself and decode from memory. ## The guard ``` img = cv2.imread(path, cv2.IMREAD_COLOR) if img is None: raise FileNotFoundError(f"could not decode image: {path}") ``` Three things to get right: - Use `is None`, not truthiness. `if not img:` on a real image raises `ValueError: The truth value of an array with more than one element is ambiguous`, so the guard itself becomes a bug. - Do not call `.size`, `.shape` or `len()` before the None check — those are exactly the calls that fail. - Include the path in the message. Half the debugging time on this bug is discovering which path was actually attempted; `os.path.abspath(path)` in the message ends the argument about working directories immediately. If you want to distinguish *missing* from *undecodable*, check existence separately: `os.path.exists(path)` tells you whether the file is there, and a `None` from `imread` on an existing file means the bytes are not a supported image. ## Reading from bytes When the source is not a local path — an HTTP response, a database blob, a non-ASCII filename — decode from memory instead: ``` buf = np.frombuffer(data, dtype=np.uint8) img = cv2.imdecode(buf, cv2.IMREAD_COLOR) ``` `cv2.imdecode` has the same failure contract: `None`, never an exception. The mirror image is `cv2.imencode(".png", img)`, which returns a `(retval, buffer)` pair whose first element is a success flag — a different convention worth remembering, because forgetting to unpack it gives a confusing tuple. ## The writing side of the same contract `cv2.imwrite` follows the same philosophy: it returns a boolean rather than raising for an ordinary failure such as a non-existent output directory. Code that ignores the return value happily reports success while writing nothing at all. Any pipeline that reads and writes images should check both ends. ## Why the design is like this It mirrors the C++ API, which is value-returning rather than exception-based for I/O, and OpenCV's Python layer deliberately keeps a thin, faithful mapping instead of inventing Pythonic exceptions. You are unlikely to be asked to defend the design — you are asked whether you have been bitten by it, which is why the answer interviewers want is the guard, not the philosophy.
- Why is `if not img:` a bad way to check the result?Because when the read succeeds, `img` is a multi-element NumPy array, and evaluating its truth value raises ValueError: the truth value of an array with more than one element is ambiguous. The guard then fails on the healthy path and never even reaches the failure it was meant to catch. Always compare with `is None`.
- How do you load an image whose bytes you already have in memory?Wrap the bytes in a uint8 NumPy array and call cv2.imdecode: `img = cv2.imdecode(np.frombuffer(data, np.uint8), cv2.IMREAD_COLOR)`. It has the same contract as imread — None on failure. The reverse is cv2.imencode, which returns a (success_flag, buffer) tuple you must unpack.
- Does cv2.imwrite raise if the output directory does not exist?No — it returns False. Like imread, it signals ordinary failure through the return value, so code that ignores it reports success while producing no file. Check the boolean, and create the directory before writing.
saying these in an interview costs you the question
- Claiming imread raises FileNotFoundError for a missing path
- Guarding with `if not img:` on a NumPy array
- Calling img.shape before checking for None
- Assuming a file that exists must therefore decode
- Ignoring the boolean returned by cv2.imwrite