Written on Friday May 25, 2018
At loopbio we maintain some linux packages for use with the conda package manager. These can replace the original packages present in the community-driven conda-forge channel, while retaining full compatibility with the rest of the packages in the conda-forge stack. They include some useful modifications that make them more suited to us, but that we find difficult to submit "upstream" for inclusion in the respective official packages.
Why might our packages be useful to you?
- The default OpenCV packages provided in conda are GPL due to their dependence on the conda provided FFMPEG which is build as GPL. If you are using these packages in your code, then your code is GPL (upon distribution, by the safest interpretation of the license). If you want to be sure that your code is GPL free, then use our matching LGPL-ffmpeg and OpenCV packages.
- You wish you control the number of threads OpenCV uses (via FFMPEG) for video decoding.
- You want much faster jpeg compression and decompression.
At the time of writing this note, we are actively maintaining three packages:
- ffmpeg: provides a LGPL alternative to avoid "viral" licenses in your codebase if you depend on ffmpeg but do not need H.264 encoding.
- opencv: works against any of our ffmpeg variants (giving more licensing freedom) and also using turbo for jpeg (de)compression, it also adds a few other goodies like replacing openmp with TBB as the threading managing solution or including a patch to enable controlling multi-threading when using opencv as a video decoding frontend to ffmpeg.
- libjpeg-turbo: allows to parallel install turbo with conda-forge official jpeg 9b library, enabling much faster jpeg compression and decompression while avoiding software crashes due to libjpeg libraries incompatibilities.
We have written a getting started with Conda guide here. If you are already familiar with conda then replacing your conda-forge packages with ours is a breeze. Using your command line:
# Before getting our conda packages, get a conda-forge based environment. # For example, use conda-forge by default for all your environments. conda config --add channels conda-forge # install and pin ffmpeg GPL (including libx264)... conda install 'loopbio::ffmpeg=*=*gpl*' # ...or install and pin ffmpeg LGPL (without libx264) conda install 'loopbio::ffmpeg=*=*lgpl*' # install and pin libjpeg-turbo # note, this is not needed for opencv to use libjpeg-turbo conda install 'loopbio::libjpeg-turbo=1.5.90=noclob_prefixed_gcc48_*' # install and pin opencv conda install 'loopbio::opencv=3.4.3=*h6df427c*'
If you use these packages and find any problem, please let us know using each package issue tracker.
Example: controlling ffmpeg number of threads when used through OpenCV VideoCapture
We have added an environment variable OPENCV_FFMPEG_THREAD_COUNT that controls ffmpeg's thread_count, and a capture read-only property cv2.CAP_PROP_THREAD_COUNT that can be queried to get the number of threads used by a VideoCapture object. The reason why an environment variable is needed and the property is read only is that the number of threads is a property that needs to be set early in ffmpeg's lifecycle and should not really be modified once the video reader is open. Note that threading support actually depends on the codec used to encode the video (some codecs might, for example, ignore setting thread_count). At the moment we do not support changing the threading strategy type (usually one of slice or frame).
The following are a few functions that help controlling the number of threads used by ffmpeg when decoding a video via opencv VideoCapture objects.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199
"""OpenCV utils.""" import contextlib import os import cv2 import logging _log = logging.getLogger(__package__) @contextlib.contextmanager def cv2_num_threads(num_threads): """Context manager to temporarily change the number of threads used by opencv.""" old_num_threads = cv2.getNumThreads() cv2.setNumThreads(num_threads) yield cv2.setNumThreads(old_num_threads) # A string to request not to change the current value of an envvar USE_CURRENT_VALUE = object() @contextlib.contextmanager def envvar(name, value=USE_CURRENT_VALUE): """ Context manager to temporarily change the value of an environment variable for the current process. Remember that some envvars only affects the process on startup (e.g. LD_LIBRARY_PATH). Parameters ---------- name : string The name of the environment value to modify. value : None, `cv2utils.USE_CURRENT_VALUE` or object; default "USE_CURRENT_VALUE" If `cv2utils.USE_CURRENT_VALUE`, the environment variable value is not modified whatsoever. If None, the environment variable value is temporarily removed, if it exists. Else, str(value) will be temporarily set as the value for the environment variable Examples -------- When a variable is not already set... >>> name = 'AN_ENVIRONMENT_VARIABLE' >>> with envvar(name, None): ... print(os.environ.get(name)) None >>> with envvar(name, USE_CURRENT_VALUE): ... print(os.environ.get(name)) None >>> with envvar(name, 42): ... print(os.environ.get(name)) 42 >>> print(os.environ.get(name)) None When a variable is already set... >>> os.environ[name] = 'a_default_value' >>> with envvar(name, USE_CURRENT_VALUE): ... print(os.environ.get(name)) a_default_value >>> with envvar(name, None): ... print(os.environ.get(name)) None >>> print(os.environ.get(name)) a_default_value >>> with envvar(name, 42): ... print(os.environ.get(name)) 42 >>> print(os.environ.get(name)) a_default_value """ if value is USE_CURRENT_VALUE: yield elif name not in os.environ: if value is not None: os.environ[name] = str(value) yield del os.environ[name] else: yield else: old_value = os.environ[name] if value is not None: os.environ[name] = str(value) else: del os.environ[name] yield os.environ[name] = old_value def ffmpeg_thread_count(thread_count=USE_CURRENT_VALUE): """ Context manager to temporarily change the number of threads requested by cv2.VideoCapture. This works manipulating global state, so this function is not thread safe. Take care if you instantiate capture objects with different thread_count concurrently. The actual behavior depends on the codec. Some codecs will honor thread_count, while others will not. You can always call `video_capture_thread_count(cap)` to check whether the concrete codec used does one thing or the other. Note that as of 2018/03, we only support changing the number of threads for decoding (i.e. VideoCapture, but not VideoWriter). Parameters ---------- thread_count : int or None or `cv2utils.USE_CURRENT_VALUE`, default USE * if None, then no change on the default behavior of opencv will happen on opencv 3.4.1 and linux, this means "the number of logical cores as reported by "sysconf(SC_NPROCESSORS_ONLN)" - which is a pretty aggresive setting in terms of resource consumption, specially in multiprocess applications, and might even be problematic if running with capped resources, like in a cgroups/container, under tasksel or numactl. * if an integer, set capture decoders to the specifiednumber of threads usually 0 means "auto", that is, let ffmpeg decide * if `cv2utils.USE_CURRENT_VALUE`, the current value of the environment variable OPENCV_FFMPEG_THREAD_COUNT is used (if undefined, then the default value given by opencv is used) """ return envvar(name='OPENCV_FFMPEG_THREAD_COUNT', value=thread_count) def cv2_supports_thread_count(): """Returns True iff opencv has been built with support to expose ffmpeg thread_count.""" return hasattr(cv2, 'CAP_PROP_THREAD_COUNT') def video_capture_thread_count(cap): """ Returns the number of threads used by a VideoCapture as reported by opencv. Returns None if the opencv build does not support this feature. """ try: # noinspection PyUnresolvedReferences return cap.get(cv2.CAP_PROP_THREAD_COUNT) except AttributeError: return None def open_video_capture(path, num_threads=USE_CURRENT_VALUE, fail_if_unsupported_num_threads=False, backend=cv2.CAP_FFMPEG): """ Returns a VideoCapture object for the specified path. Parameters ---------- path : string The path to a video source (file or device) num_threads : None, int or `cv2utils.USE_CURRENT_VALUE`, default None The number of threads used for decoding. If None, opencv defaults is used (number of logical cores in the system). If an int, the number of threads to use. Usually 0 means "auto", 1 "single-threaded" (but it might depend on the codec). fail_if_unsupported_num_threads : bool, default False If False, an warning is cast if num_threads is not None and setting the number of threads is unsupported either by opencv or the used codec. If True, a ValueError is raised in any of these two cases. backend : cv2 backend or None, default cv2.CAP_FFMPEG If provided, it will be used as preferred backend for opencv VidecCapture """ if num_threads is not None and not cv2_supports_thread_count(): message = ('OpenCV does not support setting the number of threads to %r; ' 'use loopbio build' % num_threads) if fail_if_unsupported_num_threads: raise ValueError(message) else: _log.warn(message) with ffmpeg_thread_count(num_threads): if backend is not None: cap = cv2.VideoCapture(path, backend) else: cap = cv2.VideoCapture(path) if cap is None or not cap.isOpened(): raise IOError("OpenCV unable to open %s" % path) if num_threads is USE_CURRENT_VALUE: try: num_threads = float(os.environ['OPENCV_FFMPEG_THREAD_COUNT']) except (KeyError, TypeError): num_threads = None if num_threads is not None and num_threads != video_capture_thread_count(cap): message = 'OpenCV num_threads for decoder setting to %r ignored for %s' % (num_threads, path) if fail_if_unsupported_num_threads: raise ValueError(message) else: _log.warn(message) return cap
If you get these functions, you can open and read capture like this:
1 2 3 4
# Do whatever you need if not cap.isOpened(): raise Exception('Something is wrong and the capture is not open') retval, image = cap.read()
Hoping other people find these packages useful.