mirror of
https://github.com/Harshit-Dhanwalkar/AsciiCam.git
synced 2026-06-12 10:35:13 +02:00
Add soble edge detection
This commit is contained in:
parent
c1f0298dae
commit
9eaf33ea36
4 changed files with 71 additions and 14 deletions
|
|
@ -7,12 +7,13 @@
|
|||
#define ASCII_CHARS_DEFAULT " .:-=+*#%@"
|
||||
|
||||
typedef struct {
|
||||
int brightness; /* additive offset applied to gray: -128..128 */
|
||||
int contrast; /* multiplier in percent; 100 = no change */
|
||||
int invert; /* non-zero: flip brightness -> charset mapping */
|
||||
int color; /* non-zero: emit ANSI truecolor escape codes */
|
||||
int dither; /* non-zero: apply Floyd-Steinberg dithering */
|
||||
const char *charset; /* custom charset string; NULL -> ASCII_CHARS_DEFAULT */
|
||||
int brightness;
|
||||
int contrast;
|
||||
int invert;
|
||||
int color;
|
||||
int edges;
|
||||
int dither;
|
||||
const char *charset;
|
||||
} ascii_opts_t;
|
||||
|
||||
// Convert YUYV raw data to grayscale
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#include "ascii.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
|
@ -50,6 +51,31 @@ size_t ascii_out_size(int dst_w, int dst_h, int color) {
|
|||
}
|
||||
}
|
||||
|
||||
// Sobel edge detection (kernel convolution)
|
||||
static void sobel(const uint8_t *in, uint8_t *out, int w, int h) {
|
||||
static const int Gx[3][3] = {{-1, 0, 1}, {-2, 0, -2}, {-1, 0, 1}};
|
||||
static const int Gy[3][3] = {{-1, -2, -1}, {0, 0, 0}, {1, 2, 1}};
|
||||
|
||||
for (int y = 1; y < h - 1; y++) {
|
||||
for (int x = 1; x < w - 1; x++) {
|
||||
int gx = 0, gy = 0;
|
||||
for (int ky = -1; ky <= 1; ky++) {
|
||||
for (int kx = -1; kx <= 1; kx++) {
|
||||
int p = in[(y + ky) * w + (x + kx)];
|
||||
gx += Gx[ky + 1][kx + 1] * p;
|
||||
gy += Gy[ky + 1][kx + 1] * p;
|
||||
}
|
||||
}
|
||||
|
||||
// TEST: test both L1 and L2 normalisations
|
||||
int mag = abs(gx) + abs(gy); // L1 normalisation
|
||||
// int mag = sqrt(gx * gx + gy * gy); // L2 normalisation
|
||||
|
||||
out[y * w + x] = (uint8_t)(mag > 255 ? 255 : mag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Grayscale to ascii
|
||||
int grayscale_to_ascii(const uint8_t *gray, const uint8_t *rgb, int src_w,
|
||||
int src_h, int dst_w, int dst_h, char *out,
|
||||
|
|
@ -61,6 +87,7 @@ int grayscale_to_ascii(const uint8_t *gray, const uint8_t *rgb, int src_w,
|
|||
int contrast = opts ? opts->contrast : 100;
|
||||
int invert = opts ? opts->invert : 0;
|
||||
int do_color = opts && opts->color && (rgb != NULL);
|
||||
int do_edges = opts ? opts->edges : 0;
|
||||
int do_dither = opts ? opts->dither : 0;
|
||||
|
||||
// Blocking Widht and height pixels in source pixels
|
||||
|
|
@ -123,6 +150,18 @@ int grayscale_to_ascii(const uint8_t *gray, const uint8_t *rgb, int src_w,
|
|||
}
|
||||
}
|
||||
|
||||
// Sobel edge detection
|
||||
if (do_edges) {
|
||||
// Temporary buffer for detected edges results
|
||||
uint8_t *edge_buf = calloc(dst_w * dst_h, sizeof(uint8_t));
|
||||
if (edge_buf) {
|
||||
sobel(small_g, edge_buf, dst_w, dst_h);
|
||||
// overwite grayscale image with edge map
|
||||
memcpy(small_g, edge_buf, dst_w * dst_h);
|
||||
free(edge_buf);
|
||||
}
|
||||
}
|
||||
|
||||
// Floyd-Steinberg dithering
|
||||
if (do_dither) {
|
||||
int16_t *eb = malloc(dst_w * dst_h * sizeof(int16_t));
|
||||
|
|
|
|||
19
C/src/main.c
19
C/src/main.c
|
|
@ -1,18 +1,18 @@
|
|||
#include "ascii.h"
|
||||
#include "capture.h"
|
||||
#include "timing.h"
|
||||
#include "thread_sharing.h"
|
||||
#include "timing.h"
|
||||
|
||||
#include <getopt.h>
|
||||
#include <pthread.h>
|
||||
#include <signal.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/mman.h>
|
||||
#include <termios.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include <pthread.h>
|
||||
#include <stdint.h>
|
||||
|
||||
// Defaults
|
||||
#define DEFAULT_ASCII_WIDTH 80
|
||||
|
|
@ -51,6 +51,7 @@ static void print_usage(const char *prog) {
|
|||
" -b <val> brightness offset -128..128 (default: 0)\n"
|
||||
" -c <val> contrast in percent >0; 100=none (default: 100)\n"
|
||||
" -i invert brightness->charset mapping\n"
|
||||
" -e enable Sobel edge detection\n"
|
||||
" -C colour output (ANSI truecolor)\n"
|
||||
" -D Floyd-Steinberg dithering\n",
|
||||
prog, DEFAULT_CAPTURE_WIDTH, DEFAULT_CAPTURE_HEIGHT, DEFAULT_FPS,
|
||||
|
|
@ -62,7 +63,7 @@ void term_raw_mode(void) {
|
|||
tcgetattr(STDOUT_FILENO, &orig_terminal);
|
||||
struct termios raw = orig_terminal;
|
||||
raw.c_lflag &= ~(ICANON | ECHO); // no line buffering or no echo
|
||||
raw.c_cc[VMIN] = 0; // non-blocking read
|
||||
raw.c_cc[VMIN] = 0; // non-blocking read
|
||||
raw.c_cc[VTIME] = 0;
|
||||
tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw);
|
||||
}
|
||||
|
|
@ -87,13 +88,14 @@ int main(int argc, char *argv[]) {
|
|||
.contrast = 100,
|
||||
.invert = 0,
|
||||
.color = 0,
|
||||
.edges = 0,
|
||||
.dither = 0,
|
||||
.charset = NULL,
|
||||
};
|
||||
|
||||
// CLI parsing
|
||||
int opt;
|
||||
while ((opt = getopt(argc, argv, "d:W:H:w:h:f:b:c:iCDs:")) != -1) {
|
||||
while ((opt = getopt(argc, argv, "ed:W:H:w:h:f:b:c:iCDs:")) != -1) {
|
||||
switch (opt) {
|
||||
case 'd':
|
||||
device = optarg;
|
||||
|
|
@ -137,6 +139,9 @@ int main(int argc, char *argv[]) {
|
|||
case 'C':
|
||||
opts.color = 1;
|
||||
break;
|
||||
case 'e':
|
||||
opts.edges = 1;
|
||||
break;
|
||||
case 'D':
|
||||
opts.dither = 1;
|
||||
break;
|
||||
|
|
@ -159,8 +164,8 @@ int main(int argc, char *argv[]) {
|
|||
}
|
||||
fprintf(stderr, "Device: %s | capture %dx%d | ASCII %dx%d | %d fps%s%s%s\n",
|
||||
device, cam.width, cam.height, ascii_w, ascii_h, fps,
|
||||
opts.color ? " | color" : "", opts.dither ? " | dither" : "",
|
||||
opts.invert ? " | inverted" : "");
|
||||
opts.color ? " | color" : "", opts.edges ? " | edges" : "",
|
||||
opts.dither ? " | dither" : "", opts.invert ? " | inverted" : "");
|
||||
|
||||
// Allocate pixel buffers
|
||||
int cam_pixels = cam.width * cam.height;
|
||||
|
|
|
|||
14
README.md
14
README.md
|
|
@ -3,15 +3,27 @@ Ascii video output from webcam in terminal.
|
|||
|
||||
<img src="assets/demo.gif" width="325">
|
||||
|
||||
## Build and Run
|
||||
```
|
||||
git clone https://github.com/Harshit-Dhanwalkar/AsciiCam.git
|
||||
cd AsciiCam/C/
|
||||
make
|
||||
|
||||
cd build/
|
||||
./webcam_ascii --help
|
||||
```
|
||||
|
||||
|
||||
## TODO
|
||||
|
||||
- [x] Adjust width and height of capturing frame.
|
||||
- [x] A producer/consumer thread splitting.
|
||||
- [ ] Custom ASCII charset via config file
|
||||
- [x] Brightness/contrast adjustment.
|
||||
- [x] Reverse video - Invert brightness $\rightarrow$ charset mapping
|
||||
- [x] Color output - Extract U/V channels, map to ANSI/RGB codes
|
||||
- [ ] Add feature to record and save it in popular video formats like `.mp4`, `.mov` and `.gif`.
|
||||
- [x] Dithering effect.
|
||||
- [x] A producer/consumer thread splitting.
|
||||
- [x] Sobel edge detection (kernel convolution). Algorithm reference: https://homepages.inf.ed.ac.uk/rbf/HIPR2/sobel.htm
|
||||
|
||||
- [ ] Migrate from C to Cpp.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue