OpenCV on macOS is commonly installed for Python scripts that read images, process frames, or test computer-vision code before it moves into a larger project. A project-local virtual environment keeps the cv2 package tied to one workspace instead of mixing it with other Python installs on the Mac.
The Python wheel published as opencv-python includes the OpenCV runtime needed by the cv2 import, so a separate source build is not needed for ordinary Python image-processing work. Homebrew still matters when the Mac does not already have a current Python 3 command; the Homebrew opencv formula fits C++ projects that need OpenCV libraries under the Homebrew prefix.
Use only one OpenCV wheel family inside the same virtual environment. Install opencv-python for the standard desktop package, switch to opencv-contrib-python when contrib modules are required, or choose a headless package only for non-GUI environments that do not call cv2.imshow.
$ python3 --version Python 3.14.6
On macOS 12.3 and later, Python may not be present until developer tools or Homebrew install it. If this command fails, install Homebrew Python with brew install python and rerun the check.
$ mkdir -p ~/opencv-demo
$ cd ~/opencv-demo
$ python3 -m venv .venv
$ source .venv/bin/activate
The prompt may show .venv while the environment is active. Use the matching activation command again in new Terminal windows before running project scripts.
$ python -m pip install --upgrade pip
$ python -m pip install opencv-python Collecting opencv-python ##### snipped ##### Successfully installed numpy-2.5.0 opencv-python-4.13.0.92
The package version and NumPy version change over time. Install opencv-contrib-python instead only when a project needs contrib modules, and do not install both packages in the same environment.
$ python -c "import cv2; print(cv2.__version__)" 4.13.0
$ python - <<'PY'
import cv2
import numpy as np
image = np.zeros((120, 160, 3), dtype=np.uint8)
image[:, :80] = (0, 128, 255)
image[:, 80:] = (0, 255, 0)
cv2.imwrite("opencv-smoke.png", image)
loaded = cv2.imread("opencv-smoke.png")
print(loaded.shape)
PY
(120, 160, 3)
The printed shape confirms that OpenCV wrote the PNG file and read it back as a 120 by 160 pixel, three-channel image.
$ rm opencv-smoke.png