Added Command Line Webcam Video

master
Javidx9 7 years ago committed by GitHub
parent a87e4abf4b
commit 94a092011d
  1. 362
      OneLoneCoder_Webcam.cpp

@ -0,0 +1,362 @@
/*
OneLoneCoder.com - Command Line Video
"Yuck..." - @Javidx9
Disclaimer
~~~~~~~~~~
I don't care what you use this for. It's intended to be educational, and perhaps
to the oddly minded - a little bit of fun. Please hack this, change it and use it
in any way you see fit. BUT, you acknowledge that I am not responsible for anything
bad that happens as a result of your actions. However, if good stuff happens, I
would appreciate a shout out, or at least give the blog some publicity for me.
Cheers!
Background
~~~~~~~~~~
I was interested in trying to recreate video that is displayed using font
characters at the command line. Mixing the colours is quite challenging
given the constraints of the palette and characters.
You'll need the ESCAPI libary binaries!
https://github.com/jarikomppa/escapi
Author
~~~~~~
Twitter: @javidx9
Blog: www.onelonecoder.com
Video:
~~~~~~
https://youtu.be/pk1Y_26j1Y4
Last Updated: 02/10/2017
*/
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
#include "olcConsoleGameEngine.h"
#include "escapi.h"
class OneLoneCoder_Video : public olcConsoleGameEngine
{
public:
OneLoneCoder_Video()
{
m_sAppName = L"Video";
}
private:
int nCameras = 0;
SimpleCapParams capture;
protected:
virtual bool OnUserCreate()
{
nCameras = setupESCAPI();
if (nCameras == 0)
return false;
capture.mWidth = ScreenWidth();
capture.mHeight = ScreenHeight();
capture.mTargetBuf = new int[ScreenWidth() * ScreenHeight()];
if (initCapture(0, &capture) == 0)
return false;
return true;
}
void ClassifyPixel_Grey(float r, float g, float b, wchar_t &sym, short &fg_col, short &bg_col)
{
float luminance = 0.2987f * r + 0.5870f * g + 0.1140f * b;
int pixel_bw = (int)(luminance * 13.0f);
switch (pixel_bw)
{
case 0: bg_col = BG_BLACK; fg_col = FG_BLACK; sym = PIXEL_SOLID; break;
case 1: bg_col = BG_BLACK; fg_col = FG_DARK_GREY; sym = PIXEL_QUARTER; break;
case 2: bg_col = BG_BLACK; fg_col = FG_DARK_GREY; sym = PIXEL_HALF; break;
case 3: bg_col = BG_BLACK; fg_col = FG_DARK_GREY; sym = PIXEL_THREEQUARTERS; break;
case 4: bg_col = BG_BLACK; fg_col = FG_DARK_GREY; sym = PIXEL_SOLID; break;
case 5: bg_col = BG_DARK_GREY; fg_col = FG_GREY; sym = PIXEL_QUARTER; break;
case 6: bg_col = BG_DARK_GREY; fg_col = FG_GREY; sym = PIXEL_HALF; break;
case 7: bg_col = BG_DARK_GREY; fg_col = FG_GREY; sym = PIXEL_THREEQUARTERS; break;
case 8: bg_col = BG_DARK_GREY; fg_col = FG_GREY; sym = PIXEL_SOLID; break;
case 9: bg_col = BG_GREY; fg_col = FG_WHITE; sym = PIXEL_QUARTER; break;
case 10: bg_col = BG_GREY; fg_col = FG_WHITE; sym = PIXEL_HALF; break;
case 11: bg_col = BG_GREY; fg_col = FG_WHITE; sym = PIXEL_THREEQUARTERS; break;
case 12: bg_col = BG_GREY; fg_col = FG_WHITE; sym = PIXEL_SOLID; break;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Taken from https://stackoverflow.com/questions/3018313/algorithm-to-convert-rgb-to-hsv-and-hsv-to-rgb-in-range-0-255-for-both
typedef struct
{
float r; // a fraction between 0 and 1
float g; // a fraction between 0 and 1
float b; // a fraction between 0 and 1
} rgb;
typedef struct
{
float h; // angle in degrees
float s; // a fraction between 0 and 1
float v; // a fraction between 0 and 1
} hsv;
hsv rgb2hsv(rgb in)
{
hsv out;
float min, max, delta;
min = in.r < in.g ? in.r : in.g;
min = min < in.b ? min : in.b;
max = in.r > in.g ? in.r : in.g;
max = max > in.b ? max : in.b;
out.v = max; // v
delta = max - min;
if (delta < 0.00001f)
{
out.s = 0;
out.h = 0; // undefined, maybe nan?
return out;
}
if (max > 0.0) { // NOTE: if Max is == 0, this divide would cause a crash
out.s = (delta / max); // s
}
else {
// if max is 0, then r = g = b = 0
// s = 0, h is undefined
out.s = 0.0;
out.h = NAN; // its now undefined
return out;
}
if (in.r >= max) // > is bogus, just keeps compilor happy
out.h = (in.g - in.b) / delta; // between yellow & magenta
else
if (in.g >= max)
out.h = 2.0 + (in.b - in.r) / delta; // between cyan & yellow
else
out.h = 4.0 + (in.r - in.g) / delta; // between magenta & cyan
out.h *= 60.0; // degrees
if (out.h < 0.0)
out.h += 360.0;
return out;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void ClassifyPixel_HSL(float r, float g, float b, wchar_t &sym, short &fg_col, short &bg_col)
{
hsv col = rgb2hsv({ r, g, b });
const struct { wchar_t c; short fg; short bg; } hues[] =
{
{ PIXEL_SOLID, FG_RED | BG_RED },
{ PIXEL_QUARTER, FG_YELLOW | BG_RED },
{ PIXEL_HALF, FG_YELLOW | BG_RED },
{ PIXEL_THREEQUARTERS, FG_YELLOW | BG_RED },
{ PIXEL_SOLID, FG_GREEN | BG_YELLOW },
{ PIXEL_QUARTER, FG_GREEN | BG_YELLOW },
{ PIXEL_HALF, FG_GREEN | BG_YELLOW },
{ PIXEL_THREEQUARTERS, FG_GREEN | BG_YELLOW },
{ PIXEL_SOLID, FG_CYAN | BG_GREEN },
{ PIXEL_QUARTER, FG_CYAN | BG_GREEN },
{ PIXEL_HALF, FG_CYAN | BG_GREEN },
{ PIXEL_THREEQUARTERS, FG_CYAN | BG_GREEN },
{ PIXEL_SOLID, FG_BLUE | BG_CYAN },
{ PIXEL_QUARTER, FG_BLUE | BG_CYAN },
{ PIXEL_HALF, FG_BLUE | BG_CYAN },
{ PIXEL_THREEQUARTERS, FG_BLUE | BG_CYAN },
{ PIXEL_SOLID, FG_MAGENTA | BG_BLUE },
{ PIXEL_QUARTER, FG_MAGENTA | BG_BLUE },
{ PIXEL_HALF, FG_MAGENTA | BG_BLUE },
{ PIXEL_THREEQUARTERS, FG_MAGENTA | BG_BLUE },
{ PIXEL_SOLID, FG_RED | BG_MAGENTA },
{ PIXEL_QUARTER, FG_RED | BG_MAGENTA },
{ PIXEL_HALF, FG_RED | BG_MAGENTA },
{ PIXEL_THREEQUARTERS, FG_RED | BG_MAGENTA },
};
int index = (int)((col.h / 360.0f) * 24.0f);
if (col.s > 0.2f)
{
sym = hues[index].c;
fg_col = hues[index].fg;
bg_col = hues[index].bg;
}
else
ClassifyPixel_Grey(r, g, b, sym, fg_col, bg_col);
}
void ClassifyPixel_OLC(float r, float g, float b, wchar_t &sym, short &fg_col, short &bg_col)
{
// Is pixel coloured (i.e. RGB values exhibit significant variance)
float fMean = (r + g + b) / 3.0f;
float fRVar = (r - fMean)*(r - fMean);
float fGVar = (g - fMean)*(g - fMean);
float fBVar = (b - fMean)*(b - fMean);
float fVariance = fRVar + fGVar + fBVar;
if (fVariance < 0.0001f)
{
ClassifyPixel_Grey(r, g, b, sym, fg_col, bg_col);
}
else
{
// Pixel has colour so get dominant colour
float y = min(r, g);// (r * g);
float c = min(g, b);// (g * b);
float m = min(b, r);// (b * r);
float fMean2 = (y + c + m) / 3.0f;
float fYVar = (y - fMean2)*(y - fMean2);
float fCVar = (c - fMean2)*(c - fMean2);
float fMVar = (m - fMean2)*(m - fMean2);
float fMaxPrimaryVar = max(fRVar, fGVar);
fMaxPrimaryVar = max(fMaxPrimaryVar, fBVar);
float fMaxSecondaryVar = max(fCVar, fYVar);
fMaxSecondaryVar = max(fMaxSecondaryVar, fMVar);
float fShading = 0.5f;
auto compare = [&](float fV1, float fV2, float fC1, float fC2, short FG_LIGHT, short FG_DARK, short BG_LIGHT, short BG_DARK)
{
if (fV1 >= fV2)
{
// Primary Is Dominant, Use in foreground
fg_col = fC1 > 0.5f ? FG_LIGHT : FG_DARK;
if (fV2 < 0.0001f)
{
// Secondary is not variant, Use greyscale in background
if (fC2 >= 0.00f && fC2 < 0.25f) bg_col = BG_BLACK;
if (fC2 >= 0.25f && fC2 < 0.50f) bg_col = BG_DARK_GREY;
if (fC2 >= 0.50f && fC2 < 0.75f) bg_col = BG_GREY;
if (fC2 >= 0.75f && fC2 <= 1.00f) bg_col = BG_WHITE;
}
else
{
// Secondary is varient, Use in background
bg_col = fC2 > 0.5f ? BG_LIGHT : BG_DARK;
}
// Shade dominant over background (100% -> 0%)
fShading = ((fC1 - fC2) / 2.0f) + 0.5f;
}
if (fShading >= 0.00f && fShading < 0.20f) sym = L' ';
if (fShading >= 0.20f && fShading < 0.40f) sym = PIXEL_QUARTER;
if (fShading >= 0.40f && fShading < 0.60f) sym = PIXEL_HALF;
if (fShading >= 0.60f && fShading < 0.80f) sym = PIXEL_THREEQUARTERS;
if (fShading >= 0.80f) sym = PIXEL_SOLID;
};
if (fRVar == fMaxPrimaryVar && fYVar == fMaxSecondaryVar)
compare(fRVar, fYVar, r, y, FG_RED, FG_DARK_RED, BG_YELLOW, BG_DARK_YELLOW);
if (fRVar == fMaxPrimaryVar && fMVar == fMaxSecondaryVar)
compare(fRVar, fMVar, r, m, FG_RED, FG_DARK_RED, BG_MAGENTA, BG_DARK_MAGENTA);
if (fRVar == fMaxPrimaryVar && fCVar == fMaxSecondaryVar)
compare(fRVar, fCVar, r, c, FG_RED, FG_DARK_RED, BG_CYAN, BG_DARK_CYAN);
if (fGVar == fMaxPrimaryVar && fYVar == fMaxSecondaryVar)
compare(fGVar, fYVar, g, y, FG_GREEN, FG_DARK_GREEN, BG_YELLOW, BG_DARK_YELLOW);
if (fGVar == fMaxPrimaryVar && fCVar == fMaxSecondaryVar)
compare(fGVar, fCVar, g, c, FG_GREEN, FG_DARK_GREEN, BG_CYAN, BG_DARK_CYAN);
if (fGVar == fMaxPrimaryVar && fMVar == fMaxSecondaryVar)
compare(fGVar, fMVar, g, m, FG_GREEN, FG_DARK_GREEN, BG_MAGENTA, BG_DARK_MAGENTA);
if (fBVar == fMaxPrimaryVar && fMVar == fMaxSecondaryVar)
compare(fBVar, fMVar, b, m, FG_BLUE, FG_DARK_BLUE, BG_MAGENTA, BG_DARK_MAGENTA);
if (fBVar == fMaxPrimaryVar && fCVar == fMaxSecondaryVar)
compare(fBVar, fCVar, b, c, FG_BLUE, FG_DARK_BLUE, BG_CYAN, BG_DARK_CYAN);
if (fBVar == fMaxPrimaryVar && fYVar == fMaxSecondaryVar)
compare(fBVar, fYVar, b, y, FG_BLUE, FG_DARK_BLUE, BG_YELLOW, BG_DARK_YELLOW);
}
}
virtual bool OnUserUpdate(float fElapsedTime)
{
// Capture webcam image
doCapture(0); while (isCaptureDone(0) == 0) {}
for (int x = 0; x < capture.mWidth; x++)
for (int y = 0; y < capture.mHeight; y++)
{
// Get pixel
union RGBint
{
int rgb;
unsigned char c[4];
};
RGBint col;
int id = y * capture.mWidth + x;
col.rgb = capture.mTargetBuf[id];
int r = col.c[2];
int g = col.c[1];
int b = col.c[0];
float fR = (float)r / 255.0f;
float fG = (float)g / 255.0f;
float fB = (float)b / 255.0f;
// Convert into character/colour combo
wchar_t sym;
short bg_col = 0;
short fg_col = 0;
//ClassifyPixel_Grey(fR, fG, fB, sym, fg_col, bg_col);
//ClassifyPixel_HSL(fR, fG, fB, sym, fg_col, bg_col);
ClassifyPixel_OLC(fR, fG, fB, sym, fg_col, bg_col);
// Draw pixel
Draw(x, y, sym, bg_col | fg_col);
}
return true;
}
};
int main()
{
OneLoneCoder_Video game;
game.ConstructConsole(320, 240, 4, 4);
game.Start();
return 0;
}
Loading…
Cancel
Save