DEV Community

Cover image for How I Use My Terminal as a Webcam
Prakhar Kaushik
Prakhar Kaushik

Posted on

8 3

How I Use My Terminal as a Webcam

Te-We is a project which focuses on using a webcam just with a terminal. Powered by Python we will be creating a terminal stream of webcam.
One day I was going through my system to find a webcam application but was not able to find one because I use Debian Linux and I added each and every software manually, so I never downloaded anything for the webcam.
So I thought why not make something with a terminal for this, I came across a project which was an Image to Ascii Project and I thought why not combine the video stream with a similar project and hence TE-WE was developed


This whole thing is an Ascii Art of a video stream from the webcam.
If you find it interesting, you can find the whole unscripted programming video of it at


So getting back to the code part. Let's start with it.

Step 1

Create an Ascii Image using an actual image

Step 2

Replace the Ascii Image with a video stream frame and print them one over the other to get the effect of a continuous frame.
Done!!


Let's Discuss everything in Detail, Our first aim would be to convert

Alt Text

to an Ascii Image like

Alt Text

Once it's Done all that's left is to create a video stream as a source for frames to the program.


Let's see the code first

def renderImg(img):
# img = cv2.imread("deadpool.jpg")
width, height, _ = img.shape
aspectRatio = height / width
newWidth = 120
newHeight = int(newWidth * aspectRatio * 0.25)
img = cv2.resize(img, (newWidth, newHeight))
colorCodes = []
for row in img:
colorCodes.append([rgb2short_fast(pixel) for pixel in row])
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
chars = ["B", "S", "#", "&", "@", "$", "%", "*", "+", "!", ":", "."]
newImg = []
for rowIndex in range(len(img)):
temp = []
for cellIndex in range(len(img[rowIndex])):
print(
fg(colorCodes[rowIndex][cellIndex])
+ chars[img[rowIndex][cellIndex] // 25],
end="",
)
temp.append(
fg(colorCodes[rowIndex][cellIndex])
+ chars[img[rowIndex][cellIndex] // 25]
)
newImg.append("".join(temp))
print()
import sys
with open("test.txt", "w") as fl:
fl.writelines(newImg)
CURSOR_ONE_UP = "\033[A"
for i in range(len(img)):
sys.stdout.write(CURSOR_ONE_UP)
view raw renderImage.py hosted with ❤ by GitHub

Let me explain everything here. For now ignore the line 10–12, remaining part is simple, convert the whole RGB image to black and white image so that we can actually get the intensity of each pixel.
Then based on the intensity of each pixel we will find a suitable character for each pixel and create an image out of it.
But the problem is that its still black and white, so the next part would be to get the approx ANSI color for each pixel hence we will find the colored image printed.
Let's see the code to get RGB to ANSI, It was taken from a library

GitHub logo torrycrass / image-to-ansi

A Python based Image to ANSI converter

#! /usr/bin/env python
"""
This program provides the ability to convert an image file to ANSI compatible
output for display in terminal sessions that support ANSI code.
#
This is a derivative of the improvements made by Takumi Sueda (puhitaku) on
image-to-ansi.py at https://gist.github.com/puhitaku/7eaf6142fa5a42425f55 which
provided significant (7x) speed improvements in turn is a derivative of the
work of Kevin Lange (klange) which was originally built on from Micah Elliott
(MicahElliott) a huge thank you to all these folks for the great foundational
work that I was able to slightly modify.
This version updates a variable to allow for a string variable (filename) to
be passed at run time from cli to the program. This simplifies execution by
allowing the user to run: python image-to-ansi.py <filename>
# Major Improvement List #
- direct passing of file variable to script
- basic error checking for existance of file
- basic error checking for single variable
- addition of help section explaining syntax
- general cleanup
- slight update and rework of the header format
-torrycrass
"""
__author__ = "Micah Elliott http://MicahElliott.com; Kevin Lange <k@dakko.us>; Takumi Sueda <puhitaku@gmail.com>; Torry Crass <tc.github@outlook.com>"
__copyright__ = "Copyright (C) 2011 Micah Elliott. All rights reserved."
__credits__ = ["Micah Elliott", "Kevin Lange", "Takumi Sueda", "Torry Crass"]
__license__ = "WTFPL http://sam.zoy.org/wtfpl/"
__version__ = "0.2"
__maintainer__ = "Torry Crass"
__email__ = "tc.github@outlook.com"
__status__ = "Development"
import sys
import os.path
CLUT = [
('000000', '00'),
('800000', '01'),
('008000', '02'),
('808000', '03'),
('000080', '04'),
('800080', '05'),
('008080', '06'),
('c0c0c0', '07'),
('808080', '08'),
('ff0000', '09'),
('00ff00', '10'),
('ffff00', '11'),
('0000ff', '12'),
('ff00ff', '13'),
('00ffff', '14'),
('ffffff', '15'),
('000000', '16'),
('00005f', '17'),
('000087', '18'),
('0000af', '19'),
('0000d7', '20'),
('0000ff', '21'),
('005f00', '22'),
('005f5f', '23'),
('005f87', '24'),
('005faf', '25'),
('005fd7', '26'),
('005fff', '27'),
('008700', '28'),
('00875f', '29'),
('008787', '30'),
('0087af', '31'),
('0087d7', '32'),
('0087ff', '33'),
('00af00', '34'),
('00af5f', '35'),
('00af87', '36'),
('00afaf', '37'),
('00afd7', '38'),
('00afff', '39'),
('00d700', '40'),
('00d75f', '41'),
('00d787', '42'),
('00d7af', '43'),
('00d7d7', '44'),
('00d7ff', '45'),
('00ff00', '46'),
('00ff5f', '47'),
('00ff87', '48'),
('00ffaf', '49'),
('00ffd7', '50'),
('00ffff', '51'),
('5f0000', '52'),
('5f005f', '53'),
('5f0087', '54'),
('5f00af', '55'),
('5f00d7', '56'),
('5f00ff', '57'),
('5f5f00', '58'),
('5f5f5f', '59'),
('5f5f87', '60'),
('5f5faf', '61'),
('5f5fd7', '62'),
('5f5fff', '63'),
('5f8700', '64'),
('5f875f', '65'),
('5f8787', '66'),
('5f87af', '67'),
('5f87d7', '68'),
('5f87ff', '69'),
('5faf00', '70'),
('5faf5f', '71'),
('5faf87', '72'),
('5fafaf', '73'),
('5fafd7', '74'),
('5fafff', '75'),
('5fd700', '76'),
('5fd75f', '77'),
('5fd787', '78'),
('5fd7af', '79'),
('5fd7d7', '80'),
('5fd7ff', '81'),
('5fff00', '82'),
('5fff5f', '83'),
('5fff87', '84'),
('5fffaf', '85'),
('5fffd7', '86'),
('5fffff', '87'),
('870000', '88'),
('87005f', '89'),
('870087', '90'),
('8700af', '91'),
('8700d7', '92'),
('8700ff', '93'),
('875f00', '94'),
('875f5f', '95'),
('875f87', '96'),
('875faf', '97'),
('875fd7', '98'),
('875fff', '99'),
('878700', '100'),
('87875f', '101'),
('878787', '102'),
('8787af', '103'),
('8787d7', '104'),
('8787ff', '105'),
('87af00', '106'),
('87af5f', '107'),
('87af87', '108'),
('87afaf', '109'),
('87afd7', '110'),
('87afff', '111'),
('87d700', '112'),
('87d75f', '113'),
('87d787', '114'),
('87d7af', '115'),
('87d7d7', '116'),
('87d7ff', '117'),
('87ff00', '118'),
('87ff5f', '119'),
('87ff87', '120'),
('87ffaf', '121'),
('87ffd7', '122'),
('87ffff', '123'),
('af0000', '124'),
('af005f', '125'),
('af0087', '126'),
('af00af', '127'),
('af00d7', '128'),
('af00ff', '129'),
('af5f00', '130'),
('af5f5f', '131'),
('af5f87', '132'),
('af5faf', '133'),
('af5fd7', '134'),
('af5fff', '135'),
('af8700', '136'),
('af875f', '137'),
('af8787', '138'),
('af87af', '139'),
('af87d7', '140'),
('af87ff', '141'),
('afaf00', '142'),
('afaf5f', '143'),
('afaf87', '144'),
('afafaf', '145'),
('afafd7', '146'),
('afafff', '147'),
('afd700', '148'),
('afd75f', '149'),
('afd787', '150'),
('afd7af', '151'),
('afd7d7', '152'),
('afd7ff', '153'),
('afff00', '154'),
('afff5f', '155'),
('afff87', '156'),
('afffaf', '157'),
('afffd7', '158'),
('afffff', '159'),
('d70000', '160'),
('d7005f', '161'),
('d70087', '162'),
('d700af', '163'),
('d700d7', '164'),
('d700ff', '165'),
('d75f00', '166'),
('d75f5f', '167'),
('d75f87', '168'),
('d75faf', '169'),
('d75fd7', '170'),
('d75fff', '171'),
('d78700', '172'),
('d7875f', '173'),
('d78787', '174'),
('d787af', '175'),
('d787d7', '176'),
('d787ff', '177'),
('d7af00', '178'),
('d7af5f', '179'),
('d7af87', '180'),
('d7afaf', '181'),
('d7afd7', '182'),
('d7afff', '183'),
('d7d700', '184'),
('d7d75f', '185'),
('d7d787', '186'),
('d7d7af', '187'),
('d7d7d7', '188'),
('d7d7ff', '189'),
('d7ff00', '190'),
('d7ff5f', '191'),
('d7ff87', '192'),
('d7ffaf', '193'),
('d7ffd7', '194'),
('d7ffff', '195'),
('ff0000', '196'),
('ff005f', '197'),
('ff0087', '198'),
('ff00af', '199'),
('ff00d7', '200'),
('ff00ff', '201'),
('ff5f00', '202'),
('ff5f5f', '203'),
('ff5f87', '204'),
('ff5faf', '205'),
('ff5fd7', '206'),
('ff5fff', '207'),
('ff8700', '208'),
('ff875f', '209'),
('ff8787', '210'),
('ff87af', '211'),
('ff87d7', '212'),
('ff87ff', '213'),
('ffaf00', '214'),
('ffaf5f', '215'),
('ffaf87', '216'),
('ffafaf', '217'),
('ffafd7', '218'),
('ffafff', '219'),
('ffd700', '220'),
('ffd75f', '221'),
('ffd787', '222'),
('ffd7af', '223'),
('ffd7d7', '224'),
('ffd7ff', '225'),
('ffff00', '226'),
('ffff5f', '227'),
('ffff87', '228'),
('ffffaf', '229'),
('ffffd7', '230'),
('ffffff', '231'),
('080808', '232'),
('121212', '233'),
('1c1c1c', '234'),
('262626', '235'),
('303030', '236'),
('3a3a3a', '237'),
('444444', '238'),
('4e4e4e', '239'),
('585858', '240'),
('626262', '241'),
('6c6c6c', '242'),
('767676', '243'),
('808080', '244'),
('8a8a8a', '245'),
('949494', '246'),
('9e9e9e', '247'),
('a8a8a8', '248'),
('b2b2b2', '249'),
('bcbcbc', '250'),
('c6c6c6', '251'),
('d0d0d0', '252'),
('dadada', '253'),
('e4e4e4', '254'),
('eeeeee', '255'),
]
def _create_incs_lut():
incs = [(0x00, 0x5f), (0x5f, 0x87), (0x87, 0xaf), (0xaf, 0xd7), (0xd7, 0xff)]
res = []
for part in range(256):
for s, b in incs:
if s <= part <= b:
if abs(s - part) < abs(b - part):
res.append('%02x' % s)
else:
res.append('%02x' % b)
break
return res
RGB2SHORT_DICT = dict(CLUT)
INCS_LUT = ['00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '5f', '5f', '5f', '5f', '5f', '5f', '5f', '5f', '5f', '5f', '5f', '5f', '5f', '5f', '5f', '5f', '5f', '5f', '5f', '5f', '5f', '5f', '5f', '5f', '5f', '5f', '5f', '5f', '5f', '5f', '5f', '5f', '5f', '5f', '5f', '5f', '5f', '5f', '5f', '5f', '5f', '5f', '5f', '5f', '5f', '5f', '5f', '5f', '5f', '5f', '5f', '5f', '5f', '5f', '5f', '5f', '5f', '5f', '5f', '5f', '5f', '5f', '5f', '5f', '5f', '5f', '5f', '87', '87', '87', '87', '87', '87', '87', '87', '87', '87', '87', '87', '87', '87', '87', '87', '87', '87', '87', '87', '87', '87', '87', '87', '87', '87', '87', '87', '87', '87', '87', '87', '87', '87', '87', '87', '87', '87', '87', '87', 'af', 'af', 'af', 'af', 'af', 'af', 'af', 'af', 'af', 'af', 'af', 'af', 'af', 'af', 'af', 'af', 'af', 'af', 'af', 'af', 'af', 'af', 'af', 'af', 'af', 'af', 'af', 'af', 'af', 'af', 'af', 'af', 'af', 'af', 'af', 'af', 'af', 'af', 'af', 'af', 'd7', 'd7', 'd7', 'd7', 'd7', 'd7', 'd7', 'd7', 'd7', 'd7', 'd7', 'd7', 'd7', 'd7', 'd7', 'd7', 'd7', 'd7', 'd7', 'd7', 'd7', 'd7', 'd7', 'd7', 'd7', 'd7', 'd7', 'd7', 'd7', 'd7', 'd7', 'd7', 'd7', 'd7', 'd7', 'd7', 'd7', 'd7', 'd7', 'd7', 'ff', 'ff', 'ff', 'ff', 'ff', 'ff', 'ff', 'ff', 'ff', 'ff', 'ff', 'ff', 'ff', 'ff', 'ff', 'ff', 'ff', 'ff', 'ff', 'ff', 'ff']
def lut(part):
return INCS_LUT[part]
def rgb2short_fast(pixel):
b,g,r = pixel
return int(RGB2SHORT_DICT['%s%s%s' % (lut(r), lut(g), lut(b))])
if __name__ == '__main__':
print(rgb2short_fast((100,0 ,0 )))
view raw rgb2ansi.py hosted with ❤ by GitHub

Here is the code, It's pretty simple and actually just based on some mathematical solution.
So with this, we will get the ANSI color code from RGB and them we just need to get the ANSI color for each pixel, and here the line 10–12 will come into action
We are using their RGB value for each pixel and saving the ANSI color code separately before converting the image to black and white.
And done, our first part is done.


Step 2

Adding a video stream is simple and can be found anywhere, as it's just using opencv to get the frames from video and printing it on screen.
So here we are done.


Final Demo

Alt Text

Feedback

Do leave feedback on the project, If you want to check out the source code. Here it is

GitHub logo pr4k / Te-We

Te-We is a Terminal Webcam Client which can be used to use your webcam using terminal

Image of Datadog

How to Diagram Your Cloud Architecture

Cloud architecture diagrams provide critical visibility into the resources in your environment and how they’re connected. In our latest eBook, AWS Solution Architects Jason Mimick and James Wenzel walk through best practices on how to build effective and professional diagrams.

Download the Free eBook

Top comments (0)

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay