Grayskull: A tiny computer vision library in C for embedded systems, etc.

2 hours ago 1

Grayskull is a minimalist, dependency-free computer vision library designed for microcontrollers and other resource-constrained devices. It focuses on grayscale images and provides modern, practical algorithms that fit in a few kilobytes of code. Single-header design, integer-based operations, pure C99.

  • Image operations: copy, crop, resize (bilinear), downsample
  • Filtering: blur, Sobel edges, thresholding (global, Otsu, adaptive)
  • Morphology: erosion, dilation
  • Geometry: connected components, perspective warp
  • Features: FAST/ORB keypoints and descriptors (object tracking)
  • Local binary patterns: LBP cascades to detect faces, vehicles etc
  • Utilities: PGM read/write

As usual, no dependencies, no dynamic memory allocation, no C++, no surprises. Just a single header file under 1KLOC.

Check out the examples folder for more!

Online demo: try Grayskull in your browser.

#include "grayskull.h" struct gs_image img = gs_read_pgm("input.pgm"); struct gs_image blurred = gs_alloc(img.w, img.h); struct gs_image binary = gs_alloc(img.w, img.h); gs_blur(blurred, img, 2); gs_threshold(binary, blurred, gs_otsu_theshold(blurred)); gs_write_pgm(binary, "output.pgm"); gs_free(img); gs_free(blurred); gs_free(binary);

Note that gs_alloc/gs_free are optional helpers; you can allocate image pixel buffers any way you like.

struct gs_image { unsigned w, h; uint8_t *data; }; struct gs_rect { unsigned x, y, w, h; }; // ROI struct gs_point { unsigned x, y; }; // corners uint8_t gs_get(struct gs_image img, unsigned x, unsigned y); void gs_set(struct gs_image img, unsigned x, unsigned y, uint8_t value); void gs_crop(struct gs_image dst, struct gs_image src, struct gs_rect roi); void gs_copy(struct gs_image dst, struct gs_image src); void gs_resize(struct gs_image dst, struct gs_image src); void gs_downsample(struct gs_image dst, struct gs_image src); // Thresholding void gs_histogram(struct gs_image img, unsigned hist[256]); void gs_threshold(struct gs_image img, uint8_t threshold); uint8_t gs_otsu_threshold(struct gs_image img); void gs_adaptive_threshold(struct gs_image dst, struct gs_image src, unsigned radius, int c); // Filters void gs_blur(struct gs_image dst, struct gs_image src, unsigned radius); void gs_erode(struct gs_image dst, struct gs_image src); void gs_dilate(struct gs_image dst, struct gs_image src); void gs_sobel(struct gs_image dst, struct gs_image src); // Blobs (connected components) and contours typedef uint16_t gs_label; struct gs_blob { gs_label label; unsigned area; struct gs_rect box; struct gs_point centroid; }; struct gs_contour { struct gs_rect box; struct gs_point start; unsigned length; }; unsigned gs_blobs(struct gs_image img, gs_label *labels, struct gs_blob *blobs, unsigned nblobs); void gs_blob_corners(struct gs_image img, gs_label *labels, struct gs_blob *b, struct gs_point c[4]); void gs_perspective_correct(struct gs_image dst, struct gs_image src, struct gs_point c[4]); void gs_trace_contour(struct gs_image img, struct gs_image visited, struct gs_contour *c); // FAST/ORB struct gs_keypoint { struct gs_point pt; unsigned response; float angle; uint32_t descriptor[8]; }; struct gs_match { unsigned idx1, idx2; unsigned distance; }; unsigned gs_fast(struct gs_image img, struct gs_image scoremap, struct gs_keypoint *kps, unsigned nkps, unsigned threshold); float gs_compute_orientation(struct gs_image img, unsigned x, unsigned y, unsigned r); void gs_brief_descriptor(struct gs_image img, struct gs_keypoint *kp); unsigned gs_orb_extract(struct gs_image img, struct gs_keypoint *kps, unsigned nkps, unsigned threshold, uint8_t *scoremap_buffer); unsigned gs_match_orb(const struct gs_keypoint *kps1, unsigned n1, const struct gs_keypoint *kps2, unsigned n2, struct gs_match *matches, unsigned max_matches, float max_distance); // LBP cascades struct gs_lbp_cascade { uint16_t window_w, window_h; uint16_t nfeatures, nweaks, nstages; const int8_t *features; /* [nfeatures * 4] */ const uint16_t *weak_feature_idx; const float *weak_left_val, *weak_right_val; const uint16_t *weak_subset_offset, *weak_num_subsets; const int32_t *subsets; const uint16_t *stage_weak_start, *stage_nweaks; const float *stage_threshold; }; void gs_integral(struct gs_image src, unsigned *ii); unsigned gs_lbp_window(const struct gs_lbp_cascade *c, const unsigned *ii, unsigned iw, unsigned ih, int x, int y, float scale); unsigned gs_lbp_detect(const struct gs_lbp_cascade *c, const unsigned *ii, unsigned iw, unsigned ih, struct gs_rect *rects, unsigned max_rects, float scale_factor, float min_scale, float max_scale, int step); // Optional: struct gs_image gs_alloc(unsigned w, unsigned h); void gs_free(struct gs_image img); struct gs_image gs_read_pgm(const char *path); int gs_write_pgm(struct gs_image img, const char *path);

This project is licensed under the MIT License. Feel free to use in research, products, and your next embedded vision project!

Read Entire Article