{"id":15020375,"url":"https://github.com/glefundes/pycamloop","last_synced_at":"2025-10-25T19:30:48.832Z","repository":{"id":62579149,"uuid":"423010937","full_name":"glefundes/pycamloop","owner":"glefundes","description":"Make OpenCV camera loops less of a chore by skipping the boilerplate and getting right to the interesting stuff","archived":false,"fork":false,"pushed_at":"2023-10-04T11:44:33.000Z","size":29,"stargazers_count":10,"open_issues_count":5,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2024-09-30T09:17:35.505Z","etag":null,"topics":["opencv","python","webcam"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/glefundes.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-10-30T23:23:58.000Z","updated_at":"2023-08-22T12:10:30.000Z","dependencies_parsed_at":"2022-11-03T21:00:42.906Z","dependency_job_id":null,"html_url":"https://github.com/glefundes/pycamloop","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/glefundes%2Fpycamloop","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/glefundes%2Fpycamloop/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/glefundes%2Fpycamloop/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/glefundes%2Fpycamloop/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/glefundes","download_url":"https://codeload.github.com/glefundes/pycamloop/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":219865145,"owners_count":16555931,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["opencv","python","webcam"],"created_at":"2024-09-24T19:54:58.898Z","updated_at":"2025-10-25T19:30:48.489Z","avatar_url":"https://github.com/glefundes.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"\n[![License](https://img.shields.io/badge/license-MIT-green)](./LICENSE)\n[![PyPi version](https://badgen.net/pypi/v/pycamloop/)](https://pypi.org/project/pycamloop/)\n[![Downloads](https://pepy.tech/badge/pycamloop)](https://pepy.tech/project/pycamloop)\n\n\n\u003cbr /\u003e\n\u003cp align=\"center\"\u003e\n  \u003ch3 align=\"center\"\u003ecamloop\u003c/h3\u003e\n\n  \u003cp align=\"center\"\u003e\nForget the boilerplate from OpenCV camera loops and get to coding the interesting stuff\n    \u003cbr /\u003e\n\n\u003c/p\u003e\n\n\n\n\u003c!-- TABLE OF CONTENTS --\u003e\n## Table of Contents\n* [Usage](#usage)\n \t* [Install](#install)\n\t* [Quickstart](#quickstart)\n\t* [More advanced use cases](#more-advanced-use-cases)\n \t* [Configuring the loop](#configuring-the-loop)\n \t* [Demo](#demo)\n* [About the project](#about-the-project)\n* [TODO](#todo)\n* [License](#license)\n* [Contact](#contact)\n\n\u003c!-- USAGE --\u003e\n\n\u003c!-- USAGE --\u003e\n## Usage\nThis is a simple project developed to reduce complexity and time writing boilerplate code when prototyping computer vision applications. Stop worrying about opening/closing video caps, handling key presses, etc, and just focus on doing the cool stuff!\n\nThe project was developed in Python 3.8 and tested with physical local webcams. If you end up using it in any other context, please consider letting me know if it worked or not for whatever use case you had :)\n\n### Install\nThe project is distributed by pypi, so just:\n```bash\n$ pip install pycamloop\n```\nAs usual, conda or venv are recommended to manage your local environments.\n\n### Quickstart\nTo run a webcam loop and process each frame, just define a function that takes as argument the frame as obtained from cv2.VideoCapture's `cap()` method (i.e: a np.array) and wrap it with the `@camloop` decorator.\nYou just need to make sure your function takes the frame as an argument, and returns it so the loop can show it:\n```python\nfrom camloop import camloop\n\n@camloop()\ndef grayscale_example(frame):\n    frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)\n    return frame\n\n# calling the function will start the loop and show the results with the cv2.imshow method\ngrayscale_example()\n```\nThe window can be exited at any time by pressing \"q\" on the keyboard. You can also take screenshots at any time by pressing the \"s\" key. By default they will be saved  in the current directory (see [configuring the loop](#configuring-the-loop) for information on how to customize this and other options).\n\n\n### More advanced use cases\nNow, let's say that instead of just converting the frame to grayscale and visualizing it, you want to pass some other arguments, perform more complex operations, and/or persist information every loop. All of this can be done inside the function wrapped by the `camloop` decorator, and external dependencies can be passed as arguments to your function.\nFor example, let's say we want to run a face detector and save the results to a file called `\"face-detection-results.txt\"`:\n\n```python\nfrom camloop import camloop\n\n# for simplicity, we use cv2's own haar face detector\nface_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + \"haarcascade_frontalface_default.xml\")\n\n@camloop()\ndef face_detection_example(frame, face_cascade, results_fp=None):\n    grayscale_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)\n    faces = face_cascade.detectMultiScale(grayscale_frame, 1.2, 5)\n    for bbox in faces:\n        x1, y1 = bbox[:2]\n        x2 = x1 + bbox[2]\n        y2 = y1 + bbox[3]\n        cv2.rectangle(frame, (x1, y1), (x2, y2), (180, 0, 180), 5)\n\n    if results_fp is not None:\n\t    with open(results_fp, 'a+') as f:\n\t        f.write(f\"{datetime.datetime.now().isoformat()} - {len(faces)} face(s) found: {faces}\\n\")\n    return frame\n\nface_detection_example(face_cascade, results_fp=\"face-detection-results.txt\")\n```\n Camloop can handle any arguments and keyword arguments you define in your function, **as long as the frame is the first one**. In calling the wrapped function, pass the extra arguments with the exception of the frame which is handled implicitly.\n\n### Configuring the loop\nSince  most of the boilerplate is now hidden, `camloop` exposes a configuration object that allows the user to modify several aspects of it's behavior. The options are:\n\n| parameter       | type   | default | description                                                                                                                                                           |\n|-----------------|--------|---------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| `source`          | int    | 0       | Index of the camera to use as source for the loop (passed to cv2.VideoCapture())                                                                                      |\n| `mirror`          | bool   | False   | Whether to flip the frames horizontally                                                                                                                               |\n| `resolution`          | tuple[int, int]   | None   | Desired resolution (H,W) of the frames. Passed to the cv2.VideoCapture.set method. Default values and acceptance of custom ones depend on the webcam.                                                                                                                   |\n| `output`          | string | '.'     | Directory where to save artifacts by default (ex: captured screenshots)                                                                                               |\n| `sequence_format` | string | None    | Format for rendering sequence of frames. Acceptable formats are \"gif\" or \"mp4\". If specified a video/gif will be saved to the output folder                           |\n| `fps`             | float  | None    | FPS value used for the rendering of the sequence of frames. If unspecified, the program will try to estimate if from the length of the recording and number of frames |\n| `exit_key`        | string | 'q'     | Keyboard key used to exit the loop                                                                                                                                    |\n| `screenshot_key`  | string | 's'     | Keyboard key used to capture a screenshot                                                                                                                             |\n\nIf you want to use something other than the defaults, define a dictionary object with the desired configuration and pass it to the camloop decorator.\n\nFor example, here we want to mirror the frames horizontally, and save an MP4 video of the recording at 23.7 FPS to the `test` directory:\n\n```python\nfrom camloop import camloop\n\nconfig = {\n    'mirror': True,\n    'output': \"test/\",\n    'fps': 23.7,\n    'sequence_format': \"mp4\",\n}\n\n@camloop(config)\ndef grayscale_example(frame):\n    frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)\n    return frame\n\ngrayscale_example()\n```\n\n### Demo\nIncluded in the repo is a demonstration script that can be run out-of-the-box to verify camloop and see it's main functionalities. There are a few different samples you can check out, including the grayscale and face detection examples seen in this README).\n\nTo run the demo, install camloop and clone the repo:\n\n```bash\n$ pip install pycamloop\n$ git clone https://github.com/glefundes/pycamloop.git\n$ cd pycamloop/\n```\nThen run it by specifying which demo you want and passing any of the optional arguments (`python3 demo.py -h` for more info on them). In this case, we're mirroring the frames from the \"face detection\" demo and saving the a video of the recording in the \"demo-videos\" directory:\n\n```bash\n$ mkdir demo-videos\n$ python3 demo.py face-detection --mirror --save-sequence mp4 -o demo-videos/\n```\n\n\u003c!-- ABOUT THE PROJECT --\u003e\n## About The Project\nI work as a computer vision engineer and often find myself having to prototype or debug projects locally using my own webcam as a source. This, of course, means I have to frequently code the same boilerplate OpenCV camera loop in multiple places.\nEventually I got tired of copy-pasting the same 20 lines from file to file and decided to write a 100-ish lines package to make my work a little more efficient, less boring and code overall less bloated. That's pretty much it. Also, it was a nice chance to practice playing with decorators.\n\n\n\u003c!-- ABOUT THE PROJECT --\u003e\n## TODO\n- Verify functionality with other types of video sources (video files, streams, etc)\n\n\u003c!-- LICENSE --\u003e\n## License\nDistributed under the MIT License. See `LICENSE` for more information.\n\n\u003c!-- CONTACT --\u003e\n## Contact\n\nGabriel Lefundes Vieira - lefundes.gabriel@gmail.com\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fglefundes%2Fpycamloop","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fglefundes%2Fpycamloop","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fglefundes%2Fpycamloop/lists"}