femtoCAM/processing.cpp
2025-12-14 22:11:08 +01:00

131 lines
4 KiB
C++

#include "processing.h"
Worker::Worker(FrameBatch *batch, ArvStream *stream, ResList *result)
:batch(batch), stream(stream), result(result)
{
}
void Worker::run()
{
batch_result = new BatchResult(); // Constructed as a pointer! Will need cleanup when done!
for(int i=0; i<BATCHSIZE; i++)
{
batch_result->result[i] = processArvBuffer(batch->frames[i], cfg);
batch_result->frame_no[i] = batch->frame_no[i]; // TODO: a lot of optimisation could be done!
quint8 hits = std::min(batch_result->result[i].extended_data.length(), 9); // Max 10 hits in histogramm
batch_result->hit_hist[hits]++;
foreach(Hit hit, batch_result->result[i].extended_data)
{
int cy = std::min((hit.cy*100) / batch_result->result[i].composit.rows, 99); // TODO config size!
int cx = std::min((hit.cx*100) / batch_result->result[i].composit.cols, 99); // TODO config size!
batch_result->batch_composit.at<uchar>(cy, cx) += (uint8_t)(255/batch_result->result[i].extended_data.length());
}
}
// Append Batch Result to Output Data - with mutex!
{
QMutexLocker locker(result->resultMutex);
result->results.append(batch_result);
}
// Cleanup
for(int i=0; i<BATCHSIZE; i++)
if(ARV_IS_STREAM(stream))
arv_stream_push_buffer(stream, batch->frames[i]);
delete batch;
}
ProcessingResult processArvBuffer(ArvBuffer* buffer, const ProcessingConfig& config)
{
ProcessingResult result;
if (!buffer) {
cerr << "Error: Null ArvBuffer pointer!" << endl;
return result;
}
// --- Get image data from ArvBuffer ---
size_t data_size = 0;
const uint8_t* data = (const uint8_t*)arv_buffer_get_data(buffer, &data_size);
if (!data) {
cerr << "Error: Could not get data from ArvBuffer!" << endl;
return result;
}
int width = arv_buffer_get_image_width(buffer);
int height = arv_buffer_get_image_height(buffer);
if (width <= 0 || height <= 0) {
cerr << "Error: Invalid image dimensions!" << endl;
return result;
}
// --- Wrap raw data in a cv::Mat (no copy) ---
Mat frame(height, width, CV_8U, (void*)data);
result.composit = Mat::zeros(height, width, CV_8U);
// --- Thresholding ---
Mat frame_threshold;
if (!config.adaptive) {
cv::threshold(frame, frame_threshold, config.thresholdValue, 255, cv::THRESH_BINARY);
} else {
cv::adaptiveThreshold(frame, frame_threshold, 255, cv::ADAPTIVE_THRESH_MEAN_C,
cv::THRESH_BINARY, config.adaptiveBlocksize, -config.adaptiveShift);
}
// --- Find contours ---
vector<vector<Point>> contours;
cv::findContours(frame_threshold, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);
for (const auto& contour : contours) {
if ((int)contour.size() < config.contourMinSize) continue;
Moments m = cv::moments(contour);
if (m.m00 == 0) continue;
int cx = (int)(m.m10 / m.m00);
int cy = (int)(m.m01 / m.m00);
int area = (int)cv::contourArea(contour);
if (area < config.contourMinArea || area > config.contourMaxArea) continue;
if (cx < 0 || cy < 0 || cx >= width || cy >= height) continue;
result.composit.at<uint8_t>(cy, cx) += 1;
Hit entry;
entry.cx = cx;
entry.cy = cy;
entry.area = area;
result.extended_data.push_back(entry);
}
return result;
}
QImage drawHitsOnGrayImage(const QImage& grayImage, const ProcessingResult& result)
{
QImage output = grayImage.copy(); // make a writable copy
QPainter painter(&output);
QPen pen_fg(Qt::white);
QPen pen_bg(Qt::black);
pen_fg.setWidth(2);
pen_bg.setWidth(4);
for (const auto& hit : result.extended_data) {
int x = hit.cx;
int y = hit.cy;
painter.setPen(pen_bg);
painter.drawEllipse(QPoint(x, y), 6, 6);
painter.setPen(pen_fg);
painter.drawEllipse(QPoint(x, y), 3, 3);
}
painter.end();
return output;
}