long candy ready to test
This commit is contained in:
@@ -3,6 +3,7 @@ using OpenCvSharp;
|
||||
using Serilog;
|
||||
using System.Net;
|
||||
using System.Runtime.InteropServices;
|
||||
using Newtonsoft.Json;
|
||||
using VisionBuilder.UI.Common;
|
||||
using VisionBuilder.UI.Common.RecipeProcessing;
|
||||
using CameraSettings = Inspectron.HawkEye.Protocol.CameraSettings;
|
||||
@@ -28,31 +29,54 @@ namespace Hawkeye.VisionBuilder.UI.Sources.Hawkeye
|
||||
IPAddress.Parse(_settings.Adapter));
|
||||
_client.ImageReceived += _client_ImageReceived;
|
||||
_client.SettingsReceived += _client_SettingsReceived;
|
||||
_client.CalibrationReceived += _client_CalibrationReceived;
|
||||
_client.SupportsCalibration = _settings.IsColor;
|
||||
_client.Connect();
|
||||
Log.Information("Connected to Hawkeye camera at {Adapter}", _settings.Adapter, 27001);
|
||||
|
||||
}
|
||||
|
||||
private void _client_CalibrationReceived(List<Dictionary<double, double>> obj)
|
||||
{
|
||||
Log.Information("Calibtating...");
|
||||
_bayerFilter = new OpenCVBayerProcessor();
|
||||
_bayerFilter.SetCalibration(obj);
|
||||
}
|
||||
|
||||
private void _client_SettingsReceived(CameraSettings obj)
|
||||
{
|
||||
ApplySettings(obj);
|
||||
}
|
||||
public void ApplySettings(CameraSettings obj)
|
||||
{
|
||||
_cameraSettings = UICameraSettings.LoadSettingsLocal(_settings.SettingsFile).ToCameraSettings();
|
||||
_client.ApplySettings(_cameraSettings);
|
||||
_cameraSettings= obj;
|
||||
if (File.Exists(_settings?.SettingsFile))
|
||||
{
|
||||
_cameraSettings = UICameraSettings.LoadSettingsLocal(_settings.SettingsFile).ToCameraSettings();
|
||||
_client.ApplySettings(_cameraSettings);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public Task<Mat> GetImage(CancellationToken token)
|
||||
{
|
||||
|
||||
_lastImage = null;
|
||||
if (_imageAquisitionTaskSource != null && !_imageAquisitionTaskSource.Task.IsCanceled)
|
||||
if (_imageAquisitionTaskSource != null && !_imageAquisitionTaskSource.Task.IsCanceled && !_imageAquisitionTaskSource.Task.IsCompleted)
|
||||
{
|
||||
_imageAquisitionTaskSource.SetCanceled(CancellationToken.None);
|
||||
_imageAquisitionTaskSource = null;
|
||||
|
||||
}
|
||||
_imageAquisitionTaskSource= new TaskCompletionSource<Mat>();
|
||||
if (token.CanBeCanceled)
|
||||
{
|
||||
token.Register(() =>
|
||||
{
|
||||
_imageAquisitionTaskSource.TrySetCanceled(token);
|
||||
});
|
||||
}
|
||||
_client.Trigger();
|
||||
return _imageAquisitionTaskSource.Task;
|
||||
|
||||
@@ -61,31 +85,52 @@ namespace Hawkeye.VisionBuilder.UI.Sources.Hawkeye
|
||||
private TaskCompletionSource<Mat>? _imageAquisitionTaskSource;
|
||||
private Mat _lastImage;
|
||||
byte[] _flipBuffer = new byte[2000 * 2000];
|
||||
private OpenCVBayerProcessor _bayerFilter;
|
||||
|
||||
private void _client_ImageReceived(byte[] obj)
|
||||
{
|
||||
Log.Debug("Got image on {adapter}", _settings.Adapter);
|
||||
FlipLines(obj, _flipBuffer);
|
||||
obj = _flipBuffer;
|
||||
var pinnedArray = GCHandle.Alloc(obj, GCHandleType.Pinned);
|
||||
var pointer = pinnedArray.AddrOfPinnedObject();
|
||||
|
||||
Mat image = new Mat(_cameraSettings.ImageSettings.Lines, _cameraSettings.ImageSettings.SensorWidth,
|
||||
MatType.CV_8UC1, pointer);
|
||||
|
||||
var xCrop = _cameraSettings.ImageSettings.SensorWidth - _cameraSettings.ImageWidth - _cameraSettings.OffsetX;
|
||||
|
||||
// crop the image with opencv
|
||||
_lastImage = image[0, _cameraSettings.ImageSettings.Lines, xCrop,
|
||||
xCrop + _cameraSettings.ImageWidth].Clone();
|
||||
|
||||
if (_cameraSettings.BayerFilter)
|
||||
try
|
||||
{
|
||||
_lastImage = BayerFilter(_lastImage);
|
||||
Log.Debug("Got image on {adapter}", _settings.Adapter);
|
||||
//FlipLines(obj, _flipBuffer);
|
||||
|
||||
var pinnedArray = GCHandle.Alloc(obj, GCHandleType.Pinned);
|
||||
var pointer = pinnedArray.AddrOfPinnedObject();
|
||||
|
||||
|
||||
Mat image = new Mat(_cameraSettings.ImageSettings.Lines, _cameraSettings.ImageSettings.SensorWidth,
|
||||
MatType.CV_8UC1, pointer);
|
||||
|
||||
|
||||
|
||||
var xCrop = _cameraSettings.ImageSettings.SensorWidth - _cameraSettings.ImageWidth -
|
||||
_cameraSettings.OffsetX;
|
||||
|
||||
// crop the image with opencv
|
||||
_lastImage = image[0, _cameraSettings.ImageSettings.Lines, xCrop,
|
||||
xCrop + _cameraSettings.ImageWidth].Clone();
|
||||
|
||||
|
||||
|
||||
|
||||
if (_cameraSettings.BayerFilter)
|
||||
{
|
||||
_lastImage = _bayerFilter.ProcessBayerImageWithChannelControl(_lastImage);
|
||||
}
|
||||
|
||||
// rotate 90 degrees CCW
|
||||
Cv2.Rotate(_lastImage, _lastImage, RotateFlags.Rotate90Counterclockwise);
|
||||
|
||||
//_lastImage = _lastImage.CvtColor(ColorConversionCodes.BGR2RGB);
|
||||
|
||||
_imageAquisitionTaskSource!.SetResult(_lastImage);
|
||||
|
||||
pinnedArray.Free();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Error processing image from Hawkeye camera at {Adapter}", _settings.Adapter);
|
||||
}
|
||||
|
||||
_imageAquisitionTaskSource!.SetResult(_lastImage);
|
||||
|
||||
pinnedArray.Free();
|
||||
}
|
||||
|
||||
Mat BayerFilter(Mat image)
|
||||
@@ -96,8 +141,7 @@ namespace Hawkeye.VisionBuilder.UI.Sources.Hawkeye
|
||||
throw new ArgumentException("Input image is null or empty.", nameof(image));
|
||||
|
||||
Mat bgrImage = new Mat();
|
||||
// Use OpenCV's demosaicing function for Bayer BG pattern
|
||||
Cv2.CvtColor(image, bgrImage, ColorConversionCodes.BayerBG2BGR);
|
||||
Cv2.CvtColor(image, bgrImage, ColorConversionCodes.BayerRG2BGR);
|
||||
|
||||
return bgrImage;
|
||||
}
|
||||
@@ -127,7 +171,7 @@ namespace Hawkeye.VisionBuilder.UI.Sources.Hawkeye
|
||||
|
||||
public void InitializeModule()
|
||||
{
|
||||
|
||||
Open();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,10 +14,13 @@ public class HawkeyeSettings(string CameraName): ISettings
|
||||
[File("*.jcnf")]
|
||||
public string SettingsFile { get; set; }
|
||||
|
||||
public bool IsColor { get; set; }=true;
|
||||
|
||||
public void RegisterSettings(InspectronSettings settings)
|
||||
{
|
||||
settings.RegisterSimple(this, () => Adapter, $"{CameraName}/Sources/Hawkeye", nameof(Adapter));
|
||||
settings.RegisterSimple(this, () => IsColor, $"{CameraName}/Sources/Hawkeye", nameof(IsColor));
|
||||
|
||||
settings.RegisterSimple(this, () => SettingsFile, $"{CameraName}/Sources/Hawkeye", nameof(SettingsFile));
|
||||
}
|
||||
|
||||
|
||||
124
Hawkeye.VisionBuilder.UI.Sources.Hawkeye/OpenCVBayerFilter.cs
Normal file
124
Hawkeye.VisionBuilder.UI.Sources.Hawkeye/OpenCVBayerFilter.cs
Normal file
@@ -0,0 +1,124 @@
|
||||
using NLog.Filters;
|
||||
using OpenCvSharp;
|
||||
|
||||
namespace Hawkeye.VisionBuilder.UI.Sources.Hawkeye;
|
||||
|
||||
public class OpenCVBayerProcessor
|
||||
{
|
||||
private SplineInterpolator GreenInterpolation { get; set; }
|
||||
private SplineInterpolator RedInterpolation { get; set; }
|
||||
private SplineInterpolator BlueInterpolation { get; set; }
|
||||
|
||||
// Cached lookup tables
|
||||
private byte[] redLut;
|
||||
private byte[] greenLut;
|
||||
private byte[] blueLut;
|
||||
private Mat _lookupTable;
|
||||
|
||||
public OpenCVBayerProcessor()
|
||||
{
|
||||
// Initialize with default linear interpolation (same as your defaults)
|
||||
GreenInterpolation = new SplineInterpolator(new Dictionary<double, double>()
|
||||
{{0,0}, { 100, 100 }, { 255,255}});
|
||||
RedInterpolation = new SplineInterpolator(new Dictionary<double, double>()
|
||||
{{0,0}, { 100, 100 }, { 255,255}});
|
||||
BlueInterpolation = new SplineInterpolator(new Dictionary<double, double>()
|
||||
{{0,0}, { 100, 100 }, { 255,255}});
|
||||
|
||||
UpdateLookupTables();
|
||||
}
|
||||
|
||||
public void SetInterpolators(SplineInterpolator red, SplineInterpolator green, SplineInterpolator blue)
|
||||
{
|
||||
RedInterpolation = red;
|
||||
GreenInterpolation = green;
|
||||
BlueInterpolation = blue;
|
||||
UpdateLookupTables();
|
||||
}
|
||||
|
||||
public void SetCalibration(List<Dictionary<double, double>> calibration)
|
||||
{
|
||||
RedInterpolation = new SplineInterpolator(calibration[0]);
|
||||
GreenInterpolation = new SplineInterpolator(calibration[1]);
|
||||
BlueInterpolation = new SplineInterpolator(calibration[2]);
|
||||
UpdateLookupTables();
|
||||
}
|
||||
|
||||
private void UpdateLookupTables()
|
||||
{
|
||||
redLut = Enumerable.Range(0, 256).Select(x => (byte)RedInterpolation.GetValue(x)).ToArray();
|
||||
greenLut = Enumerable.Range(0, 256).Select(x => (byte)GreenInterpolation.GetValue(x)).ToArray();
|
||||
blueLut = Enumerable.Range(0, 256).Select(x => (byte)BlueInterpolation.GetValue(x)).ToArray();
|
||||
|
||||
// Create lookup table for all three channels
|
||||
_lookupTable = new Mat(1, 256, MatType.CV_8UC3);
|
||||
|
||||
// Use the Mat indexer for safe access
|
||||
var indexer = _lookupTable.GetGenericIndexer<Vec3b>();
|
||||
for (int i = 0; i < 256; i++)
|
||||
{
|
||||
indexer[0, i] = new Vec3b(blueLut[i], greenLut[i], redLut[i]);
|
||||
}
|
||||
}
|
||||
|
||||
public Mat ProcessBayerImage(Mat grayImage)
|
||||
{
|
||||
// Step 1: Apply Bayer demosaicing with RG pattern (matching your pattern)
|
||||
Mat colorImage = new Mat();
|
||||
Cv2.CvtColor(grayImage, colorImage, ColorConversionCodes.BayerBG2BGR);
|
||||
|
||||
// Step 2: Apply color interpolation/correction using lookup tables
|
||||
Mat correctedImage = ApplyColorCorrection(colorImage);
|
||||
|
||||
return correctedImage;
|
||||
}
|
||||
|
||||
private Mat ApplyColorCorrection(Mat colorImage)
|
||||
{
|
||||
|
||||
|
||||
// Apply the lookup table
|
||||
Mat result = new Mat();
|
||||
Cv2.LUT(colorImage, _lookupTable, result);
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Alternative: Apply correction per channel if you need more control
|
||||
public Mat ProcessBayerImageWithChannelControl(Mat grayImage)
|
||||
{
|
||||
// Step 1: Demosaic
|
||||
Mat colorImage = new Mat();
|
||||
Cv2.CvtColor(grayImage, colorImage, ColorConversionCodes.BayerBG2BGR);
|
||||
|
||||
// Step 2: Split channels
|
||||
Mat[] channels = Cv2.Split(colorImage);
|
||||
|
||||
// Step 3: Apply individual LUTs to each channel
|
||||
Mat blueCorrected = new Mat();
|
||||
Mat greenCorrected = new Mat();
|
||||
Mat redCorrected = new Mat();
|
||||
|
||||
Mat blueLutMat = new Mat(1, 256, MatType.CV_8U, blueLut);
|
||||
Mat greenLutMat = new Mat(1, 256, MatType.CV_8U, greenLut);
|
||||
Mat redLutMat = new Mat(1, 256, MatType.CV_8U, redLut);
|
||||
|
||||
Cv2.LUT(channels[0], blueLutMat, blueCorrected);
|
||||
Cv2.LUT(channels[1], greenLutMat, greenCorrected);
|
||||
Cv2.LUT(channels[2], redLutMat, redCorrected);
|
||||
|
||||
// Step 4: Merge channels back
|
||||
Mat result = new Mat();
|
||||
Cv2.Merge(new Mat[] { blueCorrected, greenCorrected, redCorrected }, result);
|
||||
|
||||
// Cleanup
|
||||
foreach (var channel in channels) channel.Dispose();
|
||||
blueCorrected.Dispose();
|
||||
greenCorrected.Dispose();
|
||||
redCorrected.Dispose();
|
||||
colorImage.Dispose();
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
138
Hawkeye.VisionBuilder.UI.Sources.Hawkeye/SplineInterpolator.cs
Normal file
138
Hawkeye.VisionBuilder.UI.Sources.Hawkeye/SplineInterpolator.cs
Normal file
@@ -0,0 +1,138 @@
|
||||
namespace Hawkeye.VisionBuilder.UI.Sources.Hawkeye;
|
||||
|
||||
public class SplineInterpolator
|
||||
{
|
||||
private readonly Dictionary<double, double> _nodes;
|
||||
private readonly double[] _keys;
|
||||
|
||||
private readonly double[] _values;
|
||||
|
||||
private readonly double[] _h;
|
||||
|
||||
private readonly double[] _a;
|
||||
|
||||
/// <summary>
|
||||
/// Class constructor.
|
||||
/// </summary>
|
||||
/// <param name="nodes">Collection of known points for further interpolation.
|
||||
/// Should contain at least two items.</param>
|
||||
public SplineInterpolator(Dictionary<double, double> nodes)
|
||||
{
|
||||
if (nodes == null)
|
||||
{
|
||||
throw new ArgumentNullException("nodes");
|
||||
}
|
||||
|
||||
_nodes = nodes;
|
||||
|
||||
var n = nodes.Count;
|
||||
|
||||
if (n < 2)
|
||||
{
|
||||
throw new ArgumentException("At least two point required for interpolation.");
|
||||
}
|
||||
|
||||
_keys = nodes.Keys.ToArray();
|
||||
_values = nodes.Values.ToArray();
|
||||
_a = new double[n];
|
||||
_h = new double[n];
|
||||
|
||||
for (int i = 1; i < n; i++)
|
||||
{
|
||||
_h[i] = _keys[i] - _keys[i - 1];
|
||||
}
|
||||
|
||||
if (n > 2)
|
||||
{
|
||||
var sub = new double[n - 1];
|
||||
var diag = new double[n - 1];
|
||||
var sup = new double[n - 1];
|
||||
|
||||
for (int i = 1; i <= n - 2; i++)
|
||||
{
|
||||
diag[i] = (_h[i] + _h[i + 1]) / 3;
|
||||
sup[i] = _h[i + 1] / 6;
|
||||
sub[i] = _h[i] / 6;
|
||||
_a[i] = (_values[i + 1] - _values[i]) / _h[i + 1] - (_values[i] - _values[i - 1]) / _h[i];
|
||||
}
|
||||
|
||||
SolveTridiag(sub, diag, sup, ref _a, n - 2);
|
||||
}
|
||||
}
|
||||
|
||||
public double[] Keys => _keys;
|
||||
|
||||
public double[] Values => _values;
|
||||
|
||||
public Dictionary<double, double> Nodes => _nodes;
|
||||
|
||||
/// <summary>
|
||||
/// Gets interpolated value for specified argument.
|
||||
/// </summary>
|
||||
/// <param name="key">Argument value for interpolation. Must be within
|
||||
/// the interval bounded by lowest ang highest <see cref="_keys"/> values.</param>
|
||||
public double GetValue(double key)
|
||||
{
|
||||
int gap = 0;
|
||||
var previous = double.MinValue;
|
||||
|
||||
|
||||
if (key > _keys.Max()) key = _keys.Max();
|
||||
if (key < _keys.Min()) key = _keys.Min();
|
||||
|
||||
for (int i = 0; i < _keys.Length; i++)
|
||||
{
|
||||
if (Math.Abs(_keys[i] - key) < 0.001)
|
||||
{
|
||||
return _values[i];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// At the end of this iteration, "gap" will contain the index of the interval
|
||||
// between two known values, which contains the unknown z, and "previous" will
|
||||
// contain the biggest z value among the known samples, left of the unknown z
|
||||
for (int i = 0; i < _keys.Length; i++)
|
||||
{
|
||||
if (_keys[i] < key && _keys[i] > previous)
|
||||
{
|
||||
previous = _keys[i];
|
||||
gap = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
var x1 = key - previous;
|
||||
var x2 = _h[gap] - x1;
|
||||
|
||||
var res = ((-_a[gap - 1] / 6 * (x2 + _h[gap]) * x1 + _values[gap - 1]) * x2 +
|
||||
(-_a[gap] / 6 * (x1 + _h[gap]) * x2 + _values[gap]) * x1) / _h[gap];
|
||||
if (res > 255) res = 255;
|
||||
if (res < 0) res = 0;
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Solve linear system with tridiagonal n*n matrix "a"
|
||||
/// using Gaussian elimination without pivoting.
|
||||
/// </summary>
|
||||
private static void SolveTridiag(double[] sub, double[] diag, double[] sup, ref double[] b, int n)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 2; i <= n; i++)
|
||||
{
|
||||
sub[i] = sub[i] / diag[i - 1];
|
||||
diag[i] = diag[i] - sub[i] * sup[i - 1];
|
||||
b[i] = b[i] - sub[i] * b[i - 1];
|
||||
}
|
||||
|
||||
b[n] = b[n] / diag[n];
|
||||
|
||||
for (i = n - 1; i >= 1; i--)
|
||||
{
|
||||
b[i] = (b[i] - sup[i] * b[i + 1]) / diag[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user