====== Circle Detector ======
> [[https://gist.github.com/nmichaud/7975c461d91229007ec75d7bad8e0d04|This is an adaptation of this gist]], thanks [[https://x.com/naveenma?lang=en|Naveen Michaud-Agrawal]]
* TODO: Replace ''cameraToProjector'' with quad translation implementation from ''virtual-programs/mask-tags.folk''
* TODO: affix relative codepaths below
* Copy the C++ code paths below onto your Folk machine (make sure your directory names match the paths below)
* Print out the Folk programs
* Try it out:
{{notes:pxl_20240601_1918326632.gif}}
[C++]: OpenCVWrapper.h
#ifndef OPENCV_WRAPPER_H
#define OPENCV_WRAPPER_H
#ifdef __cplusplus
extern "C" {
#endif
typedef struct {
uint32_t width;
uint32_t height;
int components;
uint32_t bytesPerRow;
uint8_t *data;
} cv_image_t;
typedef struct {
float x;
float y;
float radius;
} cv_keypoint_t;
uint32_t cvSimpleBlobDetector(cv_image_t img, cv_keypoint_t* kpts, uint32_t count, bool use_hough);
#ifdef __cplusplus
}
#endif
#endif // OPENCV_WRAPPER_H
[C++]: OpenCVWrapper.cpp
#include
#include
extern "C" {
#include "OpenCVWrapper.h"
cv::Mat convertToMat(const cv_image_t& img) {
int type = 0;
// Determine the correct type based on number of components
if (img.components == 1) {
type = CV_8UC1; // Grayscale
} else if (img.components == 3) {
type = CV_8UC3; // RGB
} else {
// Add more cases if needed
throw std::runtime_error("Unsupported number of components");
}
// Create cv::Mat with the given size, type, and data
// Note: The step is the number of bytes per row
return cv::Mat(img.height, img.width, type, img.data, img.bytesPerRow);
}
cv_image_t convertToImageT(const cv::Mat& mat) {
cv_image_t img;
img.width = static_cast(mat.cols);
img.height = static_cast(mat.rows);
int type = mat.type();
if (type == CV_8UC1) {
img.components = 1;
} else if (type == CV_8UC3) {
img.components = 3;
}
img.bytesPerRow = static_cast(mat.step); // or mat.step1()
// Allocate memory for the data
size_t dataSize = mat.step * mat.rows; // or mat.total() * mat.elemSize()
img.data = new uint8_t[dataSize];
// Copy the data from the cv::Mat
std::memcpy(img.data, mat.data, dataSize);
return img;
}
uint32_t cvSimpleBlobDetector(cv_image_t src, cv_keypoint_t* kpts, uint32_t count, bool use_hough) {
cv::Mat gray = convertToMat(src);
cv::Mat blur;
const int maxsize = 760;
float ratio = float(maxsize) / src.height;
if (src.height > maxsize) {
cv::resize( gray, gray, cv::Size(), ratio, ratio, cv::INTER_NEAREST);
cv::medianBlur(gray, blur, 3);
} else {
cv::medianBlur(gray, blur, 11);
}
//cv::Mat contour(blur.size(), CV_8UC3, cv::Scalar(0,0,0));
//cv::cvtColor(blur, contour, cv::COLOR_GRAY2RGB);
int maxCount = 0;
if (use_hough) {
// Use Hough transform
std::vector circles;
int minRadius = 10;
int maxRadius = 20;
int minDist = minRadius;
//cv::HoughCircles(blur, circles, cv::HOUGH_GRADIENT, 1, minDist, param1, param2, minRadius, maxRadius);
cv::HoughCircles(blur, circles, cv::HOUGH_GRADIENT_ALT, 1, minDist, 300, 0.9, minRadius, maxRadius);
maxCount = std::min(circles.size(), count);
// extract the x y coordinates of the keypoints:
for (int i=0; i keypoints;
// Setup SimpleBlobDetector parameters.
cv::SimpleBlobDetector::Params params;
// Change thresholds
params.minRepeatability = 2;
//params.thresholdStep = 150;
//params.maxThreshold = 255;
params.filterByArea = true;
//params.minArea = 3000;
//params.maxArea = 8000;
//params.minArea = 3000 / (float(maxsize) / src.height);
//params.maxArea = 8000 / (float(maxsize) / src.height);
params.filterByCircularity = true;
params.minCircularity = 0.87;
cv::Ptr detector = cv::SimpleBlobDetector::create(params);
detector->detect(blur, keypoints);
maxCount = std::min(keypoints.size(), count);
// extract the x y coordinates of the keypoints:
for (int i=0; i
[Folk] dots.folk
Wish $this is titled "Dot detector"
# Dot detector using OpenCV
set makeDotDetector {{} {
set detector DotDetector
namespace eval $detector {
set cc [c create]
$cc cflags -I$::env(HOME)/opencv
$cc include
$cc include
$cc include
$cc include "$::env(HOME)/ocv4/OpenCVWrapper.h"
::defineImageType $cc
$cc import ::Heap::cc folkHeapAlloc as folkHeapAlloc
$cc import ::Heap::cc folkHeapFree as folkHeapFree
$cc proc detect {image_t gray} Tcl_Obj* {
assert(gray.components == 1);
cv_image_t im = (cv_image_t) { .width = gray.width, .height = gray.height, .components = gray.components, .bytesPerRow = gray.bytesPerRow, .data = gray.data };
int max_count = 100;
cv_keypoint_t* detections = (cv_keypoint_t*)malloc(sizeof(cv_keypoint_t)*max_count);
int detectionCount = cvSimpleBlobDetector(im, detections, max_count, true);
Tcl_Obj* detectionObjs[detectionCount];
for (int i = 0; i < detectionCount; i++) {
cv_keypoint_t* det = &detections[i];
detectionObjs[i] = Tcl_ObjPrintf("center {%f %f} radius %f",
det->x, det->y, det->radius);
}
free(detections);
Tcl_Obj* result = Tcl_NewListObj(detectionCount, detectionObjs);
return result;
}
c loadlib $::env(HOME)/ocv4/libopencvwrapper.so
$cc compile
namespace export *
namespace ensemble create
}
return $detector
}}
# Plain detector. Runs on entire camera frame.
set detectorProcess [Start process {
set detector [apply $makeDotDetector]
Wish $::thisProcess receives statements like \
[list /someone/ claims camera /cam/ has frame /grayFrame/ at timestamp /timestamp/]
Wish $::thisProcess shares statements like \
[list /someone/ claims /process/ detects dots /dots/ at /timestamp/ in time /dotTime/]
When camera /cam/ has frame /grayFrame/ at timestamp /timestamp/ {
set dotTime [time {
set dots [$detector detect $grayFrame]
}]
Claim $::thisProcess detects dots $dots at $timestamp in time $dotTime
}
}]
When /someone/ detects dots /dots/ at /timestamp/ in time /dotTime/ & \
/obj/ has region /region/ {
set dots [lmap dot $dots { dict create center [lmap d [::cameraToProjector [dict get $dot center]] {round $d }] }]
set contained [list ]
foreach dot $dots {
if {[region contains $region [dict get $dot center]]} {
lappend contained $dot
}
}
if {[llength $contained] > 0} {
Claim $obj contains dots $contained
}
}
[Folk] slider.folk
Commit { Claim $this has value 0 }
Wish $this is outlined white
When $this has region /r/ {
set width [region width $r]
set height [region height $r]
if {$width > $height} {
set maxdim $width
set base "left"
set dir "left"
} else {
set maxdim $height
set base "bottom"
set dir "down"
}
set origin [region $base $r]
When $this contains dots /dots/ {
if {[llength $dots] == 1} {
set angle [* -1 [region angle $r]]
# Calculate position of dot in region space
set dot [dict get [lindex $dots 0] center]
set rel [::vec2 rotate [::vec2 sub $dot $origin] $angle]
set idx [expr {$width > $height ? 0 : 1 }]
# Calculate the fraction of the width indicated by the dot
set frac [expr {abs([lindex $rel $idx]) / $maxdim}]
Commit { Claim $this has value $frac }
}
}
When $this has value /v/ {
set shift [expr {(1 - $v) * ($maxdim / 2)}]
set scale [expr {$v * 100}]
set dim [expr {$width > $height ? "width" : "height"}]
set slider [region move [region scale $r $dim ${scale}%] $dir ${shift}px]
Wish to draw a polygon with points [region vertices $slider] color rgba(255,255,255,0.1)
Wish $this is labelled [expr {round($v)}]
}
}