Browse Source

3D text background elements, TrueType fonts new

master
kaqu 11 months ago
parent
commit
0e398df51d
7 changed files with 153 additions and 59 deletions
  1. +4
    -0
      README.md
  2. BIN
      blue3d.png
  3. +146
    -55
      funcam.py
  4. BIN
      red3d.png
  5. +3
    -4
      virtcam.sh
  6. BIN
      white3d.png
  7. BIN
      whiteline3d.png

+ 4
- 0
README.md View File

@ -9,6 +9,10 @@ I added a functioning one (store as /etc/akvcam/config.ini - w/ root rights!).
Also: You will need a 3rd gen. Intel i7/equivalent or better ...
17.05.20 New/improved features
3D text background elements
TrueType font support
15.05.20 New/improved features


BIN
blue3d.png View File

Before After
Width: 227  |  Height: 171  |  Size: 2.3 KiB

+ 146
- 55
funcam.py View File

@ -3,7 +3,7 @@
"""
funcam.py
camera stream modification
Camera stream modification
History:
--------
@ -16,6 +16,8 @@ History:
15.05.20/KQ Idea: How about increasing 'ROI'? Yup, works! (One of my better inspirations ... remember: Put a hat on!)
bounding box turned off by default, alarm bug fixed, finally our own RSS feed works as well ...
Argument parsing improved
16.05.20/KQ More 3D elements now
17.05.20/KQ TrueType fonts integrated (via PIL/pillow)
"""
import argparse
@ -26,7 +28,7 @@ import datetime, time
import cv2
import feedparser # To install: Activate (cv) environment, then 'pip3 install feedparser'
parser = argparse.ArgumentParser(prog='python3 funcam.py', usage='%(prog)s <args> [id] [alarm] [altb]',
parser = argparse.ArgumentParser(prog='python3 funcam.py', usage="%(prog)s --webcam <n> [--id '<ID>'][--rss '<url>'][--logo '<png>'][--alarm HH:MM][--altb '<path>'][--d3d][--ttf]",
description='A stream modification utility',
epilog="Dont't forget to redirect stdout to the video device")
parser.add_argument('--webcam', type=int, choices=[0, 1, 2, 3, 4], nargs='?', help='Webcam index 0..4, will be used to access webcam device /dev/video<n>', required=True)
@ -35,6 +37,8 @@ parser.add_argument('--rss', nargs='?', help='RSS feed link')
parser.add_argument('--logo', nargs='?', help='Station logo shown in upper left of output stream')
parser.add_argument('--alarm', nargs='?', help='Alarm time, format as HH:MM')
parser.add_argument('--altb', nargs='?', help='Alternate background image path')
parser.add_argument('--d3d', action='store_true', help='Draw w/ 3D elements')
parser.add_argument('--ttf', action='store_true', help='Use TrueType fonts')
args = parser.parse_args()
print("\n<<< funcam started ... >>>",file=sys.stderr)
@ -57,10 +61,13 @@ if args.alarm != None:
else: # Default off
alarm_hh = -1
alarm_mm = -1
alternate_background = args.altb
if args.altb != None:
print("Alternate background (beta!): \'"+args.altb+"\'",file=sys.stderr)
if args.d3d:
print("3D element draw : Enabled",file=sys.stderr)
if args.ttf:
print("TrueType fonts : Enabled",file=sys.stderr)
# Prepare scroll line message as an RSS feed. Currently, this is only read once upon start ...
if args.rss != None:
@ -96,9 +103,43 @@ if args.logo != None:
scr_sign = -1 # Start shrinking
alpha = 0.5 # 0.5 (0..1)
beta = (1.0 - alpha)
x_offset = 6
x_offset = 12 #6
y_offset = 12
# Prepare 3D background elements
if args.d3d:
# Scroll line background
imgWhiteline3d = cv2.imread("whiteline3d.png",-1)
if imgWhiteline3d is None:
print("*** whiteline3d.png not found!",file=sys.stderr)
exit(-1)
imgWhiteline3d = cv2.resize(imgWhiteline3d, (500,30)) # w/h
# Clock background
imgWhite3d = cv2.imread("white3d.png",-1)
if imgWhite3d is None:
print("*** white3d.png not found!",file=sys.stderr)
exit(-1)
imgWhite3d = cv2.resize(imgWhite3d, (640-500,480-432)) # w/h
imgRed3d = cv2.imread("red3d.png",-1) # Color space adjust included (required!)
if imgRed3d is None:
print("*** red3d.png not found!",file=sys.stderr)
exit(-1)
imgRed3d = cv2.resize(imgRed3d, (640-500,480-432)) # w/h
if args.ttf: # TrueType fonts enabled?
# Pillow used for TrueType font integration
from PIL import Image, ImageFont, ImageDraw # To install: Activate (cv) environment, then 'pip3 install pillow'
# Some refs.:
# https://stackoverflow.com/questions/43232813/convert-opencv-image-format-to-pil-image-format
# https://pillow.readthedocs.io/en/2.8.1/reference/ImageFont.html
# https://stackoverflow.com/questions/37191008/load-truetype-font-to-opencv
# Use a TrueType font from /usr/shar/fonts (search for *.ttf!)
# Bold small fonts: Liberation Sans Narrow Bold, Nimbus Sans L Bold Condensed, Ubuntu Condensed Bold
regularfont = ImageFont.truetype("DejaVuSansCondensed-Bold.ttf", 44) # Height for clock
boldfont = ImageFont.truetype("DejaVuSansCondensed-Bold.ttf", 24) # Height for scroll line
# Prepare background 'greenscreen'
if alternate_background != None:
alternate_frame1 = cv2.imread(args.altb,-1)
@ -162,7 +203,7 @@ def do_alternate_background(current_frame,background_frame,alternate_frame):
kernel2 = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(5,5)) # Clear artefacts
mask = cv2.dilate(mask,kernel2,iterations=1)
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(9,9)) # Clear artefacts
mask2 = cv2.erode(mask,kernel,iterations=7)
@ -223,6 +264,10 @@ while(cap.isOpened()):
ret, frame = cap.read()
if ret==True:
s = int(time.strftime("%S", time.localtime())) # Seconds (needed later as well ...)
hh = int(time.strftime("%H", time.localtime())) # Alarm time needed for processing
mm = int(time.strftime("%M", time.localtime()))
# Now: The fun stuff! ------------------------------------------
if alternate_background != None: # Green screen look-a-like pipeline
@ -240,13 +285,27 @@ while(cap.isOpened()):
src2[:, :, 0:3],
(x_offset+((100-scr_width)//2), y_offset),
src2[:, :, 3] / 255.0)
if args.d3d:
# Scroll line background
overlay_image_alpha(frame,
imgWhiteline3d[:, :, 0:3],
(0, 450), # x,y
imgWhiteline3d[:, :, 3] / 255.0)
frame4 = cv2.cvtColor(frame,cv2.COLOR_BGRA2RGB) # Correct color space
# Emblem subtext
if args.logo != None:
cv2.putText(frame4, "hacKNnology TV",
(4,y_offset+62), # x,y
cv2.putText(frame4, " LIVE",
(4,y_offset+64), # x,y
(cv2.FONT_HERSHEY_DUPLEX), # Font
0.4, # Scaling
(255, 255, 255), # RGB
1, # Thickness
cv2.LINE_AA)
cv2.putText(frame4, "Constance/Germany",
(4,y_offset+72), # x,y
(cv2.FONT_HERSHEY_DUPLEX), # Font
0.4, # Scaling
(255, 255, 255), # RGB
@ -254,7 +313,6 @@ while(cap.isOpened()):
cv2.LINE_AA)
# Random walk text label
s = int(time.strftime("%S", time.localtime())) # Seconds (needed later as well ...)
if args.id != None:
if s < 10:
current_state = True
@ -309,60 +367,93 @@ while(cap.isOpened()):
# Bottom scroll text background
if args.rss != None:
cv2.rectangle(frame4,
(0,440), # Top/left
(640,480), # Bottom right
(255,255,255), # Color
-1) # Thickness (-1=opaque/no border
cv2.putText(frame4, msg,
(scroll_x,472), # x,y
(cv2.QT_FONT_NORMAL or cv2.QT_FONT_LIGHT), # Font
1.0, # Scaling
(64,64,64), #(0, 0, 128), # RGB
2, # Thickness
cv2.LINE_AA)
cv2.line(frame4,
(498,440), # x1,y1
(498,480), # x2,y2
(200,200,200), # Color
2) # Thickness
cv2.line(frame4,
(0,440), # x1,y1
(640,440), # x2,y2
(200,200,200), # Color
1) # Thickness
if args.d3d == False:
cv2.rectangle(frame4,
(0,450), # Top/left
(640,480), # Bottom right
(255,255,255), # Color
-1) # Thickness (-1=opaque/no border
if args.ttf:
imgPil = Image.fromarray(frame4) # Unfortunately nec. ... (OpenCV->PIL)
draw = ImageDraw.Draw(imgPil) # Get a drawing handle (?!)
draw.text((scroll_x, 452), msg, font=boldfont, fill=(16,16,16,0)) # Draw actual message
np_image = np.array(imgPil) # use numpy to convert the pil_image into a numpy array
frame4 = np_image # W/o conversion this shall be a valid cam frame already ...
else:
cv2.putText(frame4, msg,
(scroll_x,472), # x,y
(cv2.QT_FONT_NORMAL or cv2.QT_FONT_LIGHT), # Font
0.8, #1.0, # Scaling
(0,0,0), #(64,64,64), #(0, 0, 128), # RGB
1, #2, # Thickness
cv2.LINE_AA)
if args.d3d == False:
cv2.line(frame4,
(498,440), # x1,y1
(498,480), # x2,y2
(200,200,200), # Color
2) # Thickness
cv2.line(frame4,
(0,440), # x1,y1
(640,440), # x2,y2
(200,200,200), # Color
1) # Thickness
scroll_x = scroll_x - 6
if scroll_x < -lenmsg*6*3: # was: -500 ...
scroll_x = 500
# Time output
t = time.strftime("%H:%M", time.localtime()) # Was: %H:%M:%S ...
hh = int(time.strftime("%H", time.localtime()))
mm = int(time.strftime("%M", time.localtime()))
if hh==alarm_hh and mm==alarm_mm and (s % 2) == 0: # 1s blink during alarm 'minute'
cv2.rectangle(frame4,
(500,432), # Top/left
(640,480), # Bottom right
(255,0,0), # Color
-1) # Thickness (-1=opaque/no border
if args.d3d == False:
if hh==alarm_hh and mm==alarm_mm and (s % 2) == 0: # 1s blink during alarm 'minute'
cv2.rectangle(frame4,
(500,432), # Top/left
(640,480), # Bottom right
(255,0,0), # Color
-1) # Thickness (-1=opaque/no border
else:
cv2.rectangle(frame4,
(500,432), # Top/left
(640,480), # Bottom right
(160,160,160), # Color
-1) # Thickness (-1=opaque/no border
else: # Color space adjustment required within image ...
# Clock background
if hh==alarm_hh and mm==alarm_mm and (s % 2) == 0: # 1s blink during alarm 'minute'
overlay_image_alpha(frame4,
imgRed3d[:, :, 0:3],
(500, 432), # x,y
imgRed3d[:, :, 3] / 255.0)
cv2.rectangle(frame4,
(500,432), # Top/left
(638,480), # Bottom right
(255,0,0), # Color
2) # Thickness (-1=opaque/no border)
else:
overlay_image_alpha(frame4,
imgWhite3d[:, :, 0:3],
(500, 432), # x,y
imgWhite3d[:, :, 3] / 255.0)
cv2.rectangle(frame4,
(500,432), # Top/left
(638,480), # Bottom right
(80,80,80), # Color
2) # Thickness (-1=opaque/no border)
if args.ttf:
imgPil = Image.fromarray(frame4) # Unfortunately nec. ... (OpenCV->PIL)
draw = ImageDraw.Draw(imgPil) # Get a drawing handle (?!)
draw.text((506, 432), t, font=regularfont, fill=(16,16,16,0)) # Draw actual message
np_image = np.array(imgPil) # use numpy to convert the pil_image into a numpy array
frame4 = np_image # W/o conversion this shall be a valid cam frame already ...
else:
cv2.rectangle(frame4,
(500,432), # Top/left
(640,480), # Bottom right
(160,160,160), # Color
-1) # Thickness (-1=opaque/no border
cv2.rectangle(frame4,
(500,432), # Top/left
(638,480), # Bottom right
(255,0,0), # Color
2) # Thickness (-1=opaque/no border
cv2.putText(frame4, t,
(505,472), # x,y
(cv2.FONT_HERSHEY_DUPLEX), # or cv2.QT_FONT_LIGHT), # Font
1.4, # Scaling
(0, 0, 0), # RGB
4, # Thickness
cv2.LINE_AA)
cv2.putText(frame4, t,
(505,472), # x,y
(cv2.FONT_HERSHEY_DUPLEX), # or cv2.QT_FONT_LIGHT), # Font
1.4, # Scaling
(0, 0, 0), # RGB
4, # Thickness
cv2.LINE_AA)
d = time.strftime("%d:%m:%Y", time.localtime())
cv2.putText(frame4, d,


BIN
red3d.png View File

Before After
Width: 227  |  Height: 171  |  Size: 1.5 KiB

+ 3
- 4
virtcam.sh View File

@ -7,7 +7,6 @@
# 05.05.20/KQ Initial version
# 09.05.20/KQ Automatic virtual port detection (via getvirtualvideo.py), several fixed values turned into parameters
# 12.05.20/KQ Local RSS feed added, more parameters ...
# 15.05.20/KQ Arguments now mostly optional
#
# 1. Install akvcam virtual camera devices
# 2. Activate (cv) environment script
@ -44,9 +43,9 @@ echo ""
echo "*** For best video conference experience (currently, 5/2020) use chromium-browser ..."
# Alternate background (beta) not activated by default
# python3 funcam.py --webcam $WEBCAM --id "$NAMEPLATE" --logo emblem.png --rss "https://www.heise.de/security/rss/news-atom.xml" --altb alternate_background.jpg > $CAMPORT
# python3 funcam.py --webcam $WEBCAM --id "$NAMEPLATE" --logo emblem.png --rss "file://$PWD/FakeNews.xml" --alarm 21:50 > $CAMPORT
python3 funcam.py --webcam $WEBCAM --id "$NAMEPLATE" --logo emblem.png --rss "https://www.hacknology.de/index.xml" > $CAMPORT
# python3 funcam.py --webcam $WEBCAM --id "$NAMEPLATE" --logo emblem.png --rss "https://www.heise.de/security/rss/news-atom.xml" --d3d --altb alternate_background.jpg > $CAMPORT
# python3 funcam.py --webcam $WEBCAM --id "$NAMEPLATE" --logo emblem.png --rss "file://$PWD/FakeNews.xml" --alarm 21:50 --d3d > $CAMPORT
python3 funcam.py --webcam $WEBCAM --id "$NAMEPLATE" --logo emblem.png --rss "https://www.hacknology.de/index.xml" --d3d --ttf > $CAMPORT
echo "Removing virtual (camera) devices ..."
sudo rmmod akvcam.ko


BIN
white3d.png View File

Before After
Width: 227  |  Height: 171  |  Size: 924 B

BIN
whiteline3d.png View File

Before After
Width: 607  |  Height: 30  |  Size: 449 B

Loading…
Cancel
Save