Add linescan plugin for line scan camera simulation

- New plugin extracts single row/column from frames and builds line scan image
- Supports horizontal mode (extract row, stack vertically)
- Supports vertical mode (extract column, stack horizontally)
- Configurable line index and output size
- Proper caps negotiation with fixate_caps implementation
- Updated build system to include linescan plugin
- Added comprehensive README with usage examples
This commit is contained in:
yair
2025-11-18 01:10:47 +02:00
parent 07673f3f36
commit b19babd038
8 changed files with 1119 additions and 5 deletions

View File

@@ -27,12 +27,12 @@ gst-launch-1.0 idsueyesrc config-file=ini/whole-presacler64_autoexp-binningx2.in
#### Send Single Line via UDP #### Send Single Line via UDP
Extract and transmit one line from camera (daytime, 200fps): Extract and transmit one line from camera (daytime, 200fps):
```pwsh ```pwsh
gst-launch-1.0 idsueyesrc config-file=ini/100fps-10exp-2456x4pix-500top-cw-extragain.ini exposure=10 framerate=750 ! ` gst-launch-1.0 idsueyesrc config-file=ini/2456x4pix-500top-cw-extragain.ini exposure=10 framerate=750 ! `
videocrop bottom=3 ! ` videocrop bottom=3 ! `
queue ! ` queue ! `
udpsink host=10.81.2.183 port=5000 udpsink host=10.81.2.183 port=5000
``` ```
now moving to now moving to automatic exposure (see [gstintervalometer](gst\intervalometer\gstintervalometer.c))
```pwsh ```pwsh
uv run .\scripts\launch-ids.py ` uv run .\scripts\launch-ids.py `
--config .\ini\2456x4pix-500top-cw.ini ` --config .\ini\2456x4pix-500top-cw.ini `
@@ -44,6 +44,16 @@ uv run .\scripts\launch-ids.py `
--host 10.81.2.183 --host 10.81.2.183
``` ```
```pwsh
gst-launch-1.0 idsueyesrc config-file=ini/2456x4pix-500top-cw-extragain.ini exposure=0.85 framerate=750 gain=0 name=cam device-id=1 ! `
intervalometer enabled=true camera-element=cam `
ramp-rate=vslow `
update-interval=1000 `
gain-ma52 `
log-file=timelapse.csv ! `
videocrop bottom=3 ! queue ! udpsink host=10.81.2.183 port=5000
```
#### Receive and Display #### Receive and Display
```pwsh ```pwsh
uv run .\scripts\recv_raw_rolling.py --display-fps 60 uv run .\scripts\recv_raw_rolling.py --display-fps 60
@@ -77,7 +87,7 @@ gst-launch-1.0 -v `
``` ```
moving to moving to
```pwsh ```pwsh
uv run .\scripts\launch-ids.py --config .\ini\100fps-10exp-2456x4pix-500top-cw-extragain.ini --device-id 1 --framerate 750 --exposure 10 --host 10.81.2.183 --gain 52 uv run .\scripts\launch-ids.py --config .\ini\2456x4pix-500top-cw-extragain.ini --device-id 1 --framerate 750 --exposure 10 --host 10.81.2.183 --gain 52
``` ```
## WIP - Frame filtering based on column analysis ## WIP - Frame filtering based on column analysis

View File

@@ -99,9 +99,10 @@ if ($BuildType -eq 'IDSuEyeOnly') {
$pluginPaths = @( $pluginPaths = @(
@{ Name = "libgstidsueye.dll"; RelPath = "sys\idsueye\Release\libgstidsueye.dll" }, @{ Name = "libgstidsueye.dll"; RelPath = "sys\idsueye\Release\libgstidsueye.dll" },
@{ Name = "libgstrollingsum.dll"; RelPath = "gst\rollingsum\Release\libgstrollingsum.dll" }, @{ Name = "libgstrollingsum.dll"; RelPath = "gst\rollingsum\Release\libgstrollingsum.dll" },
@{ Name = "libgstintervalometer.dll"; RelPath = "gst\intervalometer\Release\libgstintervalometer.dll" } @{ Name = "libgstintervalometer.dll"; RelPath = "gst\intervalometer\Release\libgstintervalometer.dll" },
@{ Name = "libgstlinescan.dll"; RelPath = "gst\linescan\Release\libgstlinescan.dll" }
) )
Write-Host "Building: All plugins (IDS uEye, Rolling Sum, Intervalometer)" -ForegroundColor Yellow Write-Host "Building: All plugins (IDS uEye, Rolling Sum, Intervalometer, Linescan)" -ForegroundColor Yellow
} }
Write-Host "" Write-Host ""
@@ -294,6 +295,7 @@ try {
Write-Host " gst-inspect-1.0 idsueyesrc" -ForegroundColor White Write-Host " gst-inspect-1.0 idsueyesrc" -ForegroundColor White
Write-Host " gst-inspect-1.0 rollingsum" -ForegroundColor White Write-Host " gst-inspect-1.0 rollingsum" -ForegroundColor White
Write-Host " gst-inspect-1.0 intervalometer" -ForegroundColor White Write-Host " gst-inspect-1.0 intervalometer" -ForegroundColor White
Write-Host " gst-inspect-1.0 linescan" -ForegroundColor White
Write-Host "" Write-Host ""
Write-Host "Example pipelines:" -ForegroundColor Yellow Write-Host "Example pipelines:" -ForegroundColor Yellow
Write-Host " # Rolling sum detection:" -ForegroundColor Gray Write-Host " # Rolling sum detection:" -ForegroundColor Gray
@@ -301,6 +303,9 @@ try {
Write-Host "" Write-Host ""
Write-Host " # Auto-exposure time-lapse:" -ForegroundColor Gray Write-Host " # Auto-exposure time-lapse:" -ForegroundColor Gray
Write-Host " gst-launch-1.0 idsueyesrc name=cam ! intervalometer enabled=true camera-element=cam exposure-min=0.85 exposure-max=1.24 gain-min=0 gain-max=52 ! autovideosink" -ForegroundColor White Write-Host " gst-launch-1.0 idsueyesrc name=cam ! intervalometer enabled=true camera-element=cam exposure-min=0.85 exposure-max=1.24 gain-min=0 gain-max=52 ! autovideosink" -ForegroundColor White
Write-Host ""
Write-Host " # Line scan camera (horizontal):" -ForegroundColor Gray
Write-Host " gst-launch-1.0 idsueyesrc ! linescan direction=horizontal line-index=100 output-size=800 ! videoconvert ! autovideosink" -ForegroundColor White
} }
Write-Host "" Write-Host ""

View File

@@ -5,6 +5,7 @@ endif (OPENCV_FOUND)
add_subdirectory (bayerutils) add_subdirectory (bayerutils)
add_subdirectory (extractcolor) add_subdirectory (extractcolor)
add_subdirectory (intervalometer) add_subdirectory (intervalometer)
add_subdirectory (linescan)
if (ENABLE_KLV) if (ENABLE_KLV)
add_subdirectory (klv) add_subdirectory (klv)

View File

@@ -0,0 +1,28 @@
set (SOURCES
gstlinescan.c
)
set (HEADERS
gstlinescan.h)
include_directories (AFTER
${ORC_INCLUDE_DIR})
set (libname gstlinescan)
add_library (${libname} MODULE
${SOURCES}
${HEADERS})
target_link_libraries (${libname}
${ORC_LIBRARIES}
${GLIB2_LIBRARIES}
${GOBJECT_LIBRARIES}
${GSTREAMER_LIBRARY}
${GSTREAMER_BASE_LIBRARY}
${GSTREAMER_VIDEO_LIBRARY})
if (WIN32)
install (FILES $<TARGET_PDB_FILE:${libname}> DESTINATION ${PDB_INSTALL_DIR} COMPONENT pdb OPTIONAL)
endif ()
install(TARGETS ${libname} LIBRARY DESTINATION ${PLUGIN_INSTALL_DIR})

125
gst/linescan/README.md Normal file
View File

@@ -0,0 +1,125 @@
# GStreamer Linescan Plugin
## Overview
The `linescan` plugin simulates a line scan camera by extracting a single row or column of pixels from each input frame and stacking them to create a line scan image over time. This is particularly useful for analyzing fast-moving objects captured with high frame rate cameras (typically 200-750 fps).
## Features
- **Horizontal mode**: Extracts a single row from each frame and stacks them vertically
- **Vertical mode**: Extracts a single column from each frame and stacks them horizontally
- **Configurable line selection**: Choose which row/column to extract, or use the middle automatically
- **Configurable output size**: Set the number of lines to accumulate before wrapping around
- **Supports multiple pixel formats**: GRAY8, GRAY16_LE, GRAY16_BE, RGB, BGR, BGRA, RGBx
## Properties
| Property | Type | Default | Description |
|----------|------|---------|-------------|
| `direction` | enum | horizontal | Direction to extract line: `horizontal` (row) or `vertical` (column) |
| `line-index` | int | -1 | Index of row/column to extract (-1 for middle of image) |
| `output-size` | int | 800 | Number of lines to accumulate (width for horizontal mode, height for vertical mode) |
## Usage Examples
### Basic Horizontal Line Scan (Extract Row 100)
```bash
gst-launch-1.0 videotestsrc ! \
linescan direction=horizontal line-index=100 output-size=800 ! \
videoconvert ! autovideosink
```
### Vertical Line Scan (Extract Middle Column)
```bash
gst-launch-1.0 videotestsrc ! \
linescan direction=vertical line-index=-1 output-size=600 ! \
videoconvert ! autovideosink
```
### High-Speed Camera Line Scan
```bash
gst-launch-1.0 idsueyesrc framerate=500 ! \
videocrop bottom=3 ! \
linescan direction=horizontal line-index=50 output-size=1000 ! \
videoconvert ! autovideosink
```
### Save Line Scan to File
```bash
gst-launch-1.0 idsueyesrc config-file=config.ini framerate=200 ! \
linescan direction=horizontal output-size=2000 ! \
videoconvert ! \
pngenc ! filesink location=linescan.png
```
## How It Works
### Horizontal Mode (default)
1. Extracts one horizontal row from each input frame
2. The row is determined by `line-index` (or middle if -1)
3. Each extracted row is stacked vertically to build the output image
4. Output dimensions: `output-size` (width) × `input_height` (height)
5. When the buffer fills up (after `input_height` frames), it wraps around and starts overwriting from the top
### Vertical Mode
1. Extracts one vertical column from each input frame
2. The column is determined by `line-index` (or middle if -1)
3. Each extracted column is stacked horizontally to build the output image
4. Output dimensions: `input_width` (width) × `output-size` (height)
5. When the buffer fills up (after `input_width` frames), it wraps around and starts overwriting from the left
## Typical Use Cases
1. **Conveyor Belt Inspection**: Capture a continuous image of objects moving on a conveyor belt
2. **High-Speed Object Analysis**: Analyze fast-moving objects frame-by-frame at high framerates
3. **Barcode/Text Reading**: Extract a horizontal line across moving barcodes or text
4. **Web Inspection**: Continuous inspection of paper, textile, or other web materials
5. **Sports Analysis**: Track trajectory or movement patterns of fast-moving objects
## Pipeline Tips
### For Best Results
- Use a high framerate camera (200-750 fps recommended)
- Ensure consistent object motion
- Adjust `line-index` to capture the region of interest
- Set `output-size` based on expected duration or object size
- Use `videocrop` before linescan if needed to reduce processing
### Common Pipeline Patterns
```bash
# Pattern 1: Crop + Linescan + Display
camera ! videocrop ! linescan ! videoconvert ! autovideosink
# Pattern 2: Linescan + Encode + Save
camera ! linescan ! videoconvert ! x264enc ! mp4mux ! filesink
# Pattern 3: Linescan + Network Stream
camera ! linescan ! videoconvert ! jpegenc ! multipartmux ! tcpserversink
```
## Performance Considerations
- The plugin maintains an internal buffer equal to the full output image size
- Memory usage = `output_width × output_height × bytes_per_pixel`
- Processing overhead is minimal (single memcpy per frame)
- No frame buffering - processes each frame immediately
## Troubleshooting
### "Could not link" errors
- Ensure videocrop or other upstream elements provide fixed caps
- Try adding `videoconvert` before linescan if needed
### Line index out of range
- Check that `line-index` is less than input height (horizontal) or width (vertical)
- Use `-1` to automatically select the middle
### Wrapping/Rolling effect
- This is normal when the buffer fills up
- Adjust `output-size` to match your capture duration needs
## See Also
- [intervalometer](../intervalometer/README.md) - Auto-exposure control
- [rollingsum](../rollingsum/README.md) - Moving window sum detection
- [GStreamer Documentation](https://gstreamer.freedesktop.org/documentation/)

636
gst/linescan/gstlinescan.c Normal file
View File

@@ -0,0 +1,636 @@
/* GStreamer
* Copyright (C) 2024 FIXME <fixme@example.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
/**
* SECTION:element-linescan
*
* Line scan camera simulator that extracts one row or column of pixels
* from each input frame and stacks them to create a line scan image.
*
* Useful for analyzing fast-moving objects with high frame rate cameras
* (typically 200-750 fps). The user sets the output width, and the height
* is determined by the input resolution.
*
* <refsect2>
* <title>Example launch line</title>
* |[
* gst-launch-1.0 videotestsrc ! linescan direction=horizontal line-index=100 output-size=800 ! videoconvert ! autovideosink
* ]|
* Extracts row 100 from each frame and creates an 800-pixel wide output
* </refsect2>
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "gstlinescan.h"
#include <string.h>
GST_DEBUG_CATEGORY_STATIC (gst_linescan_debug);
#define GST_CAT_DEFAULT gst_linescan_debug
/* Properties */
enum
{
PROP_0,
PROP_DIRECTION,
PROP_LINE_INDEX,
PROP_OUTPUT_SIZE
};
#define DEFAULT_PROP_DIRECTION GST_LINESCAN_DIRECTION_HORIZONTAL
#define DEFAULT_PROP_LINE_INDEX -1 /* -1 means middle of image */
#define DEFAULT_PROP_OUTPUT_SIZE 800
/* GStreamer boilerplate */
#define gst_linescan_parent_class parent_class
G_DEFINE_TYPE (GstLinescan, gst_linescan, GST_TYPE_BASE_TRANSFORM);
/* Define the enum type for direction */
#define GST_TYPE_LINESCAN_DIRECTION (gst_linescan_direction_get_type ())
static GType
gst_linescan_direction_get_type (void)
{
static GType direction_type = 0;
static const GEnumValue direction_values[] = {
{GST_LINESCAN_DIRECTION_HORIZONTAL, "Extract horizontal row, stack vertically", "horizontal"},
{GST_LINESCAN_DIRECTION_VERTICAL, "Extract vertical column, stack horizontally", "vertical"},
{0, NULL, NULL}
};
if (!direction_type) {
direction_type = g_enum_register_static ("GstLinescanDirection", direction_values);
}
return direction_type;
}
static GstStaticPadTemplate gst_linescan_sink_template =
GST_STATIC_PAD_TEMPLATE ("sink",
GST_PAD_SINK,
GST_PAD_ALWAYS,
GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("{ GRAY8, GRAY16_LE, GRAY16_BE, RGB, BGR, BGRA, RGBx }"))
);
static GstStaticPadTemplate gst_linescan_src_template =
GST_STATIC_PAD_TEMPLATE ("src",
GST_PAD_SRC,
GST_PAD_ALWAYS,
GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("{ GRAY8, GRAY16_LE, GRAY16_BE, RGB, BGR, BGRA, RGBx }"))
);
/* GObject vmethod declarations */
static void gst_linescan_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec);
static void gst_linescan_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec);
static void gst_linescan_finalize (GObject * object);
/* GstBaseTransform vmethod declarations */
static gboolean gst_linescan_start (GstBaseTransform * trans);
static gboolean gst_linescan_stop (GstBaseTransform * trans);
static gboolean gst_linescan_set_caps (GstBaseTransform * trans,
GstCaps * incaps, GstCaps * outcaps);
static GstCaps * gst_linescan_transform_caps (GstBaseTransform * trans,
GstPadDirection direction, GstCaps * caps, GstCaps * filter);
static GstCaps * gst_linescan_fixate_caps (GstBaseTransform * trans,
GstPadDirection direction, GstCaps * caps, GstCaps * othercaps);
static gboolean gst_linescan_transform_size (GstBaseTransform * trans,
GstPadDirection direction, GstCaps * caps, gsize size,
GstCaps * othercaps, gsize * othersize);
static GstFlowReturn gst_linescan_transform (GstBaseTransform * trans,
GstBuffer * inbuf, GstBuffer * outbuf);
/* Helper functions */
static void gst_linescan_reset (GstLinescan * filter);
static gboolean gst_linescan_allocate_buffer (GstLinescan * filter);
static void
gst_linescan_class_init (GstLinescanClass * klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass);
GstBaseTransformClass *gstbasetransform_class =
GST_BASE_TRANSFORM_CLASS (klass);
GST_DEBUG_CATEGORY_INIT (gst_linescan_debug, "linescan", 0,
"Line Scan Camera Simulator");
/* Register GObject vmethods */
gobject_class->set_property = gst_linescan_set_property;
gobject_class->get_property = gst_linescan_get_property;
gobject_class->finalize = gst_linescan_finalize;
/* Install GObject properties */
g_object_class_install_property (gobject_class, PROP_DIRECTION,
g_param_spec_enum ("direction", "Direction",
"Direction to extract line (horizontal=row, vertical=column)",
GST_TYPE_LINESCAN_DIRECTION, DEFAULT_PROP_DIRECTION,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_LINE_INDEX,
g_param_spec_int ("line-index", "Line Index",
"Index of row/column to extract (-1 for middle)", -1, G_MAXINT,
DEFAULT_PROP_LINE_INDEX,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_OUTPUT_SIZE,
g_param_spec_int ("output-size", "Output Size",
"Number of lines to accumulate (width for horizontal, height for vertical)",
1, G_MAXINT, DEFAULT_PROP_OUTPUT_SIZE,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
/* Set element metadata */
gst_element_class_add_pad_template (gstelement_class,
gst_static_pad_template_get (&gst_linescan_sink_template));
gst_element_class_add_pad_template (gstelement_class,
gst_static_pad_template_get (&gst_linescan_src_template));
gst_element_class_set_static_metadata (gstelement_class,
"Line Scan Camera", "Filter/Effect/Video",
"Extracts single row/column from frames and builds line scan image",
"FIXME <fixme@example.com>");
/* Register GstBaseTransform vmethods */
gstbasetransform_class->start = GST_DEBUG_FUNCPTR (gst_linescan_start);
gstbasetransform_class->stop = GST_DEBUG_FUNCPTR (gst_linescan_stop);
gstbasetransform_class->set_caps = GST_DEBUG_FUNCPTR (gst_linescan_set_caps);
gstbasetransform_class->transform_caps = GST_DEBUG_FUNCPTR (gst_linescan_transform_caps);
gstbasetransform_class->fixate_caps = GST_DEBUG_FUNCPTR (gst_linescan_fixate_caps);
gstbasetransform_class->transform_size = GST_DEBUG_FUNCPTR (gst_linescan_transform_size);
gstbasetransform_class->transform = GST_DEBUG_FUNCPTR (gst_linescan_transform);
}
static void
gst_linescan_init (GstLinescan * filter)
{
/* Initialize properties */
filter->direction = DEFAULT_PROP_DIRECTION;
filter->line_index = DEFAULT_PROP_LINE_INDEX;
filter->output_size = DEFAULT_PROP_OUTPUT_SIZE;
/* Initialize internal state */
filter->line_buffer = NULL;
filter->buffer_position = 0;
filter->line_size = 0;
filter->actual_line_index = 0;
filter->frame_count = 0;
filter->video_info_valid = FALSE;
filter->output_caps_set = FALSE;
/* This is not an in-place transform */
gst_base_transform_set_in_place (GST_BASE_TRANSFORM (filter), FALSE);
}
static void
gst_linescan_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec)
{
GstLinescan *filter = GST_LINESCAN (object);
switch (prop_id) {
case PROP_DIRECTION:
filter->direction = g_value_get_enum (value);
gst_linescan_reset (filter);
break;
case PROP_LINE_INDEX:
filter->line_index = g_value_get_int (value);
break;
case PROP_OUTPUT_SIZE:
filter->output_size = g_value_get_int (value);
gst_linescan_reset (filter);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gst_linescan_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec)
{
GstLinescan *filter = GST_LINESCAN (object);
switch (prop_id) {
case PROP_DIRECTION:
g_value_set_enum (value, filter->direction);
break;
case PROP_LINE_INDEX:
g_value_set_int (value, filter->line_index);
break;
case PROP_OUTPUT_SIZE:
g_value_set_int (value, filter->output_size);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gst_linescan_finalize (GObject * object)
{
GstLinescan *filter = GST_LINESCAN (object);
if (filter->line_buffer) {
g_free (filter->line_buffer);
filter->line_buffer = NULL;
}
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static gboolean
gst_linescan_start (GstBaseTransform * trans)
{
GstLinescan *filter = GST_LINESCAN (trans);
GST_DEBUG_OBJECT (filter, "start");
filter->frame_count = 0;
filter->buffer_position = 0;
return TRUE;
}
static gboolean
gst_linescan_stop (GstBaseTransform * trans)
{
GstLinescan *filter = GST_LINESCAN (trans);
GST_DEBUG_OBJECT (filter, "stop");
if (filter->line_buffer) {
g_free (filter->line_buffer);
filter->line_buffer = NULL;
}
filter->video_info_valid = FALSE;
filter->output_caps_set = FALSE;
return TRUE;
}
static GstCaps *
gst_linescan_transform_caps (GstBaseTransform * trans,
GstPadDirection direction, GstCaps * caps, GstCaps * filter_caps)
{
GstLinescan *filter = GST_LINESCAN (trans);
GstCaps *ret, *tmp;
GstStructure *structure;
gint i;
GST_DEBUG_OBJECT (filter, "transform_caps (direction: %d) caps: %" GST_PTR_FORMAT, direction, caps);
ret = gst_caps_new_empty ();
for (i = 0; i < gst_caps_get_size (caps); i++) {
structure = gst_caps_get_structure (caps, i);
structure = gst_structure_copy (structure);
if (direction == GST_PAD_SINK) {
/* Transform sink caps to src caps (input -> output) */
if (filter->direction == GST_LINESCAN_DIRECTION_HORIZONTAL) {
/* Horizontal: extract row (width=input_width), stack vertically (height=output_size) */
/* Width stays same as input, height becomes output_size */
gst_structure_set (structure, "height", G_TYPE_INT, filter->output_size, NULL);
} else {
/* Vertical: extract column (height=input_height), stack horizontally (width=output_size) */
/* Height stays same as input, width becomes output_size */
gst_structure_set (structure, "width", G_TYPE_INT, filter->output_size, NULL);
}
} else {
/* Transform src caps to sink caps (output -> input) */
/* We need to be permissive here - input can be any size */
if (filter->direction == GST_LINESCAN_DIRECTION_HORIZONTAL) {
/* For horizontal: output height is fixed, so input height can vary */
gst_structure_remove_field (structure, "height");
gst_structure_set (structure, "height", GST_TYPE_INT_RANGE, 1, G_MAXINT, NULL);
} else {
/* For vertical: output width is fixed, so input width can vary */
gst_structure_remove_field (structure, "width");
gst_structure_set (structure, "width", GST_TYPE_INT_RANGE, 1, G_MAXINT, NULL);
}
}
gst_caps_append_structure (ret, structure);
}
/* Apply filter if provided */
if (filter_caps) {
tmp = gst_caps_intersect_full (ret, filter_caps, GST_CAPS_INTERSECT_FIRST);
gst_caps_unref (ret);
ret = tmp;
}
GST_DEBUG_OBJECT (filter, "transformed caps: %" GST_PTR_FORMAT, ret);
return ret;
}
static GstCaps *
gst_linescan_fixate_caps (GstBaseTransform * trans,
GstPadDirection direction, GstCaps * caps, GstCaps * othercaps)
{
GstLinescan *filter = GST_LINESCAN (trans);
GstStructure *structure;
GstCaps *result;
GST_DEBUG_OBJECT (filter, "fixate_caps (direction: %d) caps: %" GST_PTR_FORMAT
" othercaps: %" GST_PTR_FORMAT, direction, caps, othercaps);
result = gst_caps_make_writable (othercaps);
structure = gst_caps_get_structure (result, 0);
if (direction == GST_PAD_SINK) {
/* Fixating output caps based on input */
GstVideoInfo info;
if (gst_video_info_from_caps (&info, caps)) {
if (filter->direction == GST_LINESCAN_DIRECTION_HORIZONTAL) {
/* Horizontal: extract row, stack vertically */
/* Width stays same as input, height becomes output_size */
gst_structure_fixate_field_nearest_int (structure, "width",
GST_VIDEO_INFO_WIDTH (&info));
gst_structure_fixate_field_nearest_int (structure, "height", filter->output_size);
} else {
/* Vertical: extract column, stack horizontally */
/* Width becomes output_size, height stays same as input */
gst_structure_fixate_field_nearest_int (structure, "width", filter->output_size);
gst_structure_fixate_field_nearest_int (structure, "height",
GST_VIDEO_INFO_HEIGHT (&info));
}
}
} else {
/* Fixating input caps based on output - less constrained */
/* Just fixate to something reasonable */
gst_structure_fixate_field_nearest_int (structure, "width", 640);
gst_structure_fixate_field_nearest_int (structure, "height", 480);
}
/* Fixate other fields using parent implementation */
result = gst_caps_fixate (result);
GST_DEBUG_OBJECT (filter, "fixated caps: %" GST_PTR_FORMAT, result);
return result;
}
static gboolean
gst_linescan_transform_size (GstBaseTransform * trans,
GstPadDirection direction, GstCaps * caps, gsize size,
GstCaps * othercaps, gsize * othersize)
{
GstLinescan *filter = GST_LINESCAN (trans);
GstVideoInfo info;
if (!gst_video_info_from_caps (&info, othercaps)) {
GST_ERROR_OBJECT (filter, "Failed to parse othercaps");
return FALSE;
}
*othersize = GST_VIDEO_INFO_SIZE (&info);
GST_DEBUG_OBJECT (filter, "transform_size: %zu -> %zu", size, *othersize);
return TRUE;
}
static gboolean
gst_linescan_set_caps (GstBaseTransform * trans, GstCaps * incaps,
GstCaps * outcaps)
{
GstLinescan *filter = GST_LINESCAN (trans);
if (!gst_video_info_from_caps (&filter->video_info_in, incaps)) {
GST_ERROR_OBJECT (filter, "Failed to parse input caps");
return FALSE;
}
if (!gst_video_info_from_caps (&filter->video_info_out, outcaps)) {
GST_ERROR_OBJECT (filter, "Failed to parse output caps");
return FALSE;
}
filter->video_info_valid = TRUE;
filter->output_caps_set = TRUE;
/* Calculate actual line index if set to -1 (middle) */
if (filter->line_index < 0) {
if (filter->direction == GST_LINESCAN_DIRECTION_HORIZONTAL) {
filter->actual_line_index = GST_VIDEO_INFO_HEIGHT (&filter->video_info_in) / 2;
} else {
filter->actual_line_index = GST_VIDEO_INFO_WIDTH (&filter->video_info_in) / 2;
}
} else {
filter->actual_line_index = filter->line_index;
}
/* Allocate line buffer */
if (!gst_linescan_allocate_buffer (filter)) {
GST_ERROR_OBJECT (filter, "Failed to allocate line buffer");
return FALSE;
}
GST_INFO_OBJECT (filter, "Set caps - Input: %dx%d, Output: %dx%d, Direction: %s, Line: %d",
GST_VIDEO_INFO_WIDTH (&filter->video_info_in),
GST_VIDEO_INFO_HEIGHT (&filter->video_info_in),
GST_VIDEO_INFO_WIDTH (&filter->video_info_out),
GST_VIDEO_INFO_HEIGHT (&filter->video_info_out),
(filter->direction == GST_LINESCAN_DIRECTION_HORIZONTAL) ? "HORIZONTAL" : "VERTICAL",
filter->actual_line_index);
return TRUE;
}
static gboolean
gst_linescan_allocate_buffer (GstLinescan * filter)
{
gint output_width, output_height;
gsize pixel_stride;
if (!filter->video_info_valid) {
return FALSE;
}
/* Free old buffer if exists */
if (filter->line_buffer) {
g_free (filter->line_buffer);
filter->line_buffer = NULL;
}
output_width = GST_VIDEO_INFO_WIDTH (&filter->video_info_out);
output_height = GST_VIDEO_INFO_HEIGHT (&filter->video_info_out);
pixel_stride = GST_VIDEO_INFO_COMP_PSTRIDE (&filter->video_info_out, 0);
/* Calculate line size based on direction */
if (filter->direction == GST_LINESCAN_DIRECTION_HORIZONTAL) {
/* Extracting horizontal row: line is output_width pixels wide */
filter->line_size = output_width * pixel_stride;
} else {
/* Extracting vertical column: line is 1 pixel wide */
filter->line_size = pixel_stride;
}
/* Allocate buffer for entire output image */
filter->line_buffer = g_malloc0 (GST_VIDEO_INFO_SIZE (&filter->video_info_out));
if (!filter->line_buffer) {
GST_ERROR_OBJECT (filter, "Failed to allocate line buffer");
return FALSE;
}
filter->buffer_position = 0;
GST_DEBUG_OBJECT (filter, "Allocated line buffer: %zu bytes, line size: %zu",
GST_VIDEO_INFO_SIZE (&filter->video_info_out), filter->line_size);
return TRUE;
}
static GstFlowReturn
gst_linescan_transform (GstBaseTransform * trans, GstBuffer * inbuf,
GstBuffer * outbuf)
{
GstLinescan *filter = GST_LINESCAN (trans);
GstMapInfo map_in, map_out;
guint8 *src_line, *dest_line;
gint in_width, in_height, in_stride;
gint out_width, out_height, out_stride;
gint pixel_stride;
gint x, y;
if (!filter->video_info_valid || !filter->line_buffer) {
GST_ERROR_OBJECT (filter, "Not properly initialized");
return GST_FLOW_ERROR;
}
if (!gst_buffer_map (inbuf, &map_in, GST_MAP_READ)) {
GST_ERROR_OBJECT (filter, "Failed to map input buffer");
return GST_FLOW_ERROR;
}
if (!gst_buffer_map (outbuf, &map_out, GST_MAP_WRITE)) {
GST_ERROR_OBJECT (filter, "Failed to map output buffer");
gst_buffer_unmap (inbuf, &map_in);
return GST_FLOW_ERROR;
}
in_width = GST_VIDEO_INFO_WIDTH (&filter->video_info_in);
in_height = GST_VIDEO_INFO_HEIGHT (&filter->video_info_in);
in_stride = GST_VIDEO_INFO_PLANE_STRIDE (&filter->video_info_in, 0);
out_width = GST_VIDEO_INFO_WIDTH (&filter->video_info_out);
out_height = GST_VIDEO_INFO_HEIGHT (&filter->video_info_out);
out_stride = GST_VIDEO_INFO_PLANE_STRIDE (&filter->video_info_out, 0);
pixel_stride = GST_VIDEO_INFO_COMP_PSTRIDE (&filter->video_info_in, 0);
if (filter->direction == GST_LINESCAN_DIRECTION_HORIZONTAL) {
/* Extract horizontal row */
if (filter->actual_line_index >= in_height) {
GST_WARNING_OBJECT (filter, "Line index %d exceeds input height %d",
filter->actual_line_index, in_height);
gst_buffer_unmap (inbuf, &map_in);
gst_buffer_unmap (outbuf, &map_out);
return GST_FLOW_ERROR;
}
/* Get pointer to the row we want to extract */
src_line = map_in.data + (filter->actual_line_index * in_stride);
/* Copy the row to the current position in our buffer */
dest_line = filter->line_buffer + (filter->buffer_position * out_stride);
memcpy (dest_line, src_line, filter->line_size);
} else {
/* Extract vertical column */
if (filter->actual_line_index >= in_width) {
GST_WARNING_OBJECT (filter, "Line index %d exceeds input width %d",
filter->actual_line_index, in_width);
gst_buffer_unmap (inbuf, &map_in);
gst_buffer_unmap (outbuf, &map_out);
return GST_FLOW_ERROR;
}
/* Extract column pixel by pixel and place horizontally in output */
for (y = 0; y < in_height && y < out_height; y++) {
src_line = map_in.data + (y * in_stride) + (filter->actual_line_index * pixel_stride);
dest_line = filter->line_buffer + (y * out_stride) + (filter->buffer_position * pixel_stride);
memcpy (dest_line, src_line, pixel_stride);
}
}
/* Increment buffer position */
filter->buffer_position++;
/* Wrap around when we reach the output size */
if (filter->direction == GST_LINESCAN_DIRECTION_HORIZONTAL) {
if (filter->buffer_position >= out_height) {
filter->buffer_position = 0;
}
} else {
if (filter->buffer_position >= out_width) {
filter->buffer_position = 0;
}
}
/* Copy accumulated buffer to output */
memcpy (map_out.data, filter->line_buffer, map_out.size);
gst_buffer_unmap (inbuf, &map_in);
gst_buffer_unmap (outbuf, &map_out);
filter->frame_count++;
GST_LOG_OBJECT (filter, "Processed frame %lu, buffer position: %d",
(unsigned long) filter->frame_count, filter->buffer_position);
return GST_FLOW_OK;
}
static void
gst_linescan_reset (GstLinescan * filter)
{
if (filter->line_buffer) {
g_free (filter->line_buffer);
filter->line_buffer = NULL;
}
filter->buffer_position = 0;
filter->frame_count = 0;
if (filter->video_info_valid) {
gst_linescan_allocate_buffer (filter);
}
}
static gboolean
plugin_init (GstPlugin * plugin)
{
return gst_element_register (plugin, "linescan", GST_RANK_NONE,
GST_TYPE_LINESCAN);
}
GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
GST_VERSION_MINOR,
linescan,
"Line scan camera simulator that extracts rows/columns from frames",
plugin_init, GST_PACKAGE_VERSION, GST_PACKAGE_LICENSE, GST_PACKAGE_NAME,
GST_PACKAGE_ORIGIN)

View File

@@ -0,0 +1,87 @@
/* GStreamer
* Copyright (C) 2024 FIXME <fixme@example.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifndef __GST_LINESCAN_H__
#define __GST_LINESCAN_H__
#include <gst/base/gstbasetransform.h>
#include <gst/video/video.h>
G_BEGIN_DECLS
#define GST_TYPE_LINESCAN \
(gst_linescan_get_type())
#define GST_LINESCAN(obj) \
(G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_LINESCAN,GstLinescan))
#define GST_LINESCAN_CLASS(klass) \
(G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_LINESCAN,GstLinescanClass))
#define GST_IS_LINESCAN(obj) \
(G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_LINESCAN))
#define GST_IS_LINESCAN_CLASS(klass) \
(G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_LINESCAN))
typedef struct _GstLinescan GstLinescan;
typedef struct _GstLinescanClass GstLinescanClass;
typedef enum {
GST_LINESCAN_DIRECTION_HORIZONTAL = 0, /* Extract row from each frame, stack vertically (width=input, height=output_size) */
GST_LINESCAN_DIRECTION_VERTICAL /* Extract column from each frame, stack horizontally (width=output_size, height=input) */
} GstLinescanDirection;
/**
* GstLinescan:
* @element: the parent element.
*
* Line scan plugin that extracts a single row or column from each frame
* and builds them into an output image over time.
*/
struct _GstLinescan
{
GstBaseTransform element;
/* Properties */
GstLinescanDirection direction; /* HORIZONTAL or VERTICAL */
gint line_index; /* Which row/column to extract (-1 = middle) */
gint output_size; /* Number of lines to accumulate (width for vertical, height for horizontal) */
/* Video info */
GstVideoInfo video_info_in;
GstVideoInfo video_info_out;
gboolean video_info_valid;
/* Internal state */
guint8 *line_buffer; /* Buffer to accumulate lines */
gint buffer_position; /* Current position in buffer (how many lines accumulated) */
gsize line_size; /* Size of one line in bytes */
gint actual_line_index; /* Computed line index to extract */
guint64 frame_count; /* Number of frames processed */
gboolean output_caps_set; /* Whether output caps have been negotiated */
};
struct _GstLinescanClass
{
GstBaseTransformClass parent_class;
};
GType gst_linescan_get_type(void);
G_END_DECLS
#endif /* __GST_LINESCAN_H__ */

222
ini/roi-night.ini Normal file
View File

@@ -0,0 +1,222 @@
[Versions]
ueye_api_64.dll=4.93.1730
ueye_usb_64.sys=4.93.1314
ueye_boot_64.sys=4.93.1314
[Sensor]
Sensor=UI308xCP-C
Sensor bit depth=0
Sensor source gain=24
FPN correction mode=0
Black reference mode=0
Sensor digital gain=0
[Image size]
Start X=524
Start Y=450
Start X absolute=1
Start Y absolute=1
Width=256
Height=1008
Binning=0
Subsampling=0
[Scaler]
Mode=0
Factor=0.000000
[Multi AOI]
Enabled=0
Mode=0
x1=0
x2=0
x3=0
x4=0
y1=0
y2=0
y3=0
y4=0
[Shutter]
Mode=0
Linescan number=0
[Log Mode]
Mode=3
Manual value=0
Manual gain=0
[Timing]
Pixelclock=474
Extended pixelclock range=0
Framerate=169.724771
Exposure=5.851568
Long exposure=0
Dual exposure ratio=0
[Selected Converter]
IS_SET_CM_RGB32=2
IS_SET_CM_RGB24=2
IS_SET_CM_RGB16=2
IS_SET_CM_RGB15=2
IS_SET_CM_Y8=2
IS_SET_CM_RGB8=2
IS_SET_CM_BAYER=8
IS_SET_CM_UYVY=2
IS_SET_CM_UYVY_MONO=2
IS_SET_CM_UYVY_BAYER=2
IS_CM_CBYCRY_PACKED=0
IS_SET_CM_RGBY=8
IS_SET_CM_RGB30=2
IS_SET_CM_Y12=2
IS_SET_CM_BAYER12=8
IS_SET_CM_Y16=2
IS_SET_CM_BAYER16=8
IS_CM_BGR12_UNPACKED=2
IS_CM_BGRA12_UNPACKED=2
IS_CM_JPEG=0
IS_CM_SENSOR_RAW10=8
IS_CM_MONO10=2
IS_CM_BGR10_UNPACKED=2
IS_CM_RGBA8_PACKED=2
IS_CM_RGB8_PACKED=2
IS_CM_RGBY8_PACKED=8
IS_CM_RGB10V2_PACKED=8
IS_CM_RGB12_UNPACKED=2
IS_CM_RGBA12_UNPACKED=2
IS_CM_RGB10_UNPACKED=2
IS_CM_RGB8_PLANAR=2
[Parameters]
Colormode=1
Gamma=1.200000
Hardware Gamma=0
Blacklevel Mode=0
Blacklevel Offset=4
Hotpixel Mode=2
Hotpixel Threshold=0
Sensor Hotpixel=0
Adaptive hotpixel correction enable=0
Adaptive hotpixel correction mode=0
Adaptive hotpixel correction sensitivity=3
GlobalShutter=0
AllowRawWithLut=0
[Gain]
Master=32
Red=19
Green=0
Blue=33
GainBoost=1
[Processing]
EdgeEnhancementFactor=0
RopEffect=0
Whitebalance=0
Whitebalance Red=1.000000
Whitebalance Green=1.000000
Whitebalance Blue=1.000000
Color correction=4
Color_correction_factor=1.000000
Color_correction_satU=100
Color_correction_satV=100
Bayer Conversion=1
JpegCompression=0
NoiseMode=0
ImageEffect=0
LscModel=0
WideDynamicRange=0
[Auto features]
Auto Framerate control=0
Brightness exposure control=0
Brightness gain control=0
Auto Framerate Sensor control=0
Brightness exposure Sensor control=0
Brightness gain Sensor control=0
Brightness exposure Sensor control photometry=0
Brightness gain Sensor control photometry=0
Brightness control once=0
Brightness reference=128
Brightness speed=50
Brightness max gain=100
Brightness max exposure=5.851568
Brightness Aoi Left=524
Brightness Aoi Top=450
Brightness Aoi Width=256
Brightness Aoi Height=1008
Brightness Hysteresis=2
AutoImageControlMode=2
AutoImageControlPeakWhiteChannel=0
AutoImageControlExposureMinimum=0.000000
AutoImageControlPeakWhiteChannelMode=0
AutoImageControlPeakWhiteGranularity=0
Auto WB control=0
Auto WB type=2
Auto WB RGB color model=1
Auto WB RGB color temperature=5000
Auto WB offsetR=0
Auto WB offsetB=0
Auto WB gainMin=0
Auto WB gainMax=100
Auto WB speed=50
Auto WB Aoi Left=524
Auto WB Aoi Top=450
Auto WB Aoi Width=256
Auto WB Aoi Height=1008
Auto WB Once=0
Auto WB Hysteresis=2
Brightness Skip Frames Trigger Mode=4
Brightness Skip Frames Freerun Mode=4
Auto WB Skip Frames Trigger Mode=4
Auto WB Skip Frames Freerun Mode=4
[Trigger and Flash]
Trigger mode=0
Trigger timeout=200
Trigger delay=0
Trigger debounce mode=0
Trigger debounce delay time=1
Trigger burst size=1
Trigger prescaler frame=1
Trigger prescaler line=1
Trigger input=1
Flash strobe=0
Flash delay=0
Flash duration=0
Flash auto freerun=0
PWM mode=0
PWM frequency=20000000
PWM dutycycle=20000000
GPIO state=3
GPIO direction=0
GPIO1 Config=1
GPIO2 Config=1
[Vertical AOI Merge Mode]
Mode=0
Position=0
Additional Position=0
Height=2
[Level Controlled Trigger Mode]
Mode=0
[Memory]
Camera memory mode=1