arm/WindowsFormsApp1/IntelRealSense.cs
2025-02-04 20:09:10 +08:00

971 lines
45 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Intel.RealSense;
using Emgu.CV.Structure;
using Emgu.CV;
using System.IO;
using System.Drawing;
using System.Windows.Forms;
using Stream = Intel.RealSense.Stream;
using System.Threading;
using WindowsFormsApp1; // 根据你的项目命名空间进行修改
using ABB.Robotics.Controllers.IOSystemDomain;
using yolov5_onnx.Mod;
using static WindowsFormsApp1.ExtrinsicsExtensions;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
using NetTopologySuite.GeometriesGraph;
using System.Windows.Media.Media3D;
namespace RealSence_PathMove
{
public class IntelRealSense
{
private readonly Form1 form;
public IntelRealSense(Form1 form)
{
this.form = form;
}
/// <summary>
/// 相機連結狀態
/// </summary>
enum CameraState
{
Disconnected = 0,
Connected = 1,
Opened = 2
}
Colorizer colorizer = new Colorizer();
Align align = new Align(Intel.RealSense.Stream.Color); // 深度影像MAP彩色影像
CameraState CamState = CameraState.Disconnected; // 初始狀態設為斷線
public AdvancedDevice DeviceName;
private Pipeline pipeline = new Pipeline();
internal PictureBox pictureBox2;
internal PictureBox pictureBox3;
internal PictureBox pictureBox4;
internal PictureBox pictureBox5;
private CustomProcessingBlock block;
internal CancellationTokenSource cts;
private System.Threading.Tasks.Task dataProcessingTask = System.Threading.Tasks.Task.CompletedTask; // 初始化为已完成的任务。
VideoStreamProfile depthStream;
VideoStreamProfile colorStream;
public Intrinsics depthIntrinsics; // 定義成員變數,以便在不同方法中訪問
public Extrinsics depthExtrinsics;
public Intrinsics colorIntrinsics;
public Extrinsics colorExtrinsics;
public Bitmap bmpColor; // 請確保這裡有定義 bmpColor 成員
public Bitmap bmpDepth;
public List<System.Drawing.PointF> yoloFeaturePoints = new List<System.Drawing.PointF>();
// 假设您已经获得了特徵點在相機座標系中的坐标列表 cameraPointsList
List<MathNet.Numerics.LinearAlgebra.Vector<double>> cameraPointsList = new List<MathNet.Numerics.LinearAlgebra.Vector<double>>();
public int objectCounter = 1; // 初始化物體編號為1
StringBuilder coordTextBuilder = new StringBuilder();
private readonly object frameLock = new object();
public int deepestX, deepestY, shallowestX, shallowestY, Goldsamplepointcloudnumber;
public double X, Y, u, v, percentageInsideCrop;
public float Z, x, y, z, Rx, Ry, Rz, threshold, deepestZ, shallowestZ, GolddeepestZ, GoldshallowestZ;
public string folderPath, fileName, folderPath1, fileName1;
public ListViewItem Item { get; set; }
public bool camclose;
public Extrinsics SharedExtrinsics { get; private set; }
public ushort[] DepthData { get; private set; }
public Bitmap yoloimage { get; private set; }
public float MaxDepthValue { get; private set; }
public System.Numerics.Vector3 cameraPoint { get; private set; }
public float scale { get; private set; }
public Image<Rgb, byte> ColorImg { get; private set; }
public Image<Rgb, byte> DepthImg { get; private set; }
public Image<Rgb, byte> ROIImg;
public Bitmap bitmap { get; private set; }
public ushort[] croppedDepthData;
// 在類別中添加一個 List<Vector3> 來儲存三維座標
public List<System.Numerics.Vector3> storedCoordinates = new List<System.Numerics.Vector3>();
public Bitmap DepthBitmap { get; private set; }
public Rectangle CroppingRect { get; private set; }
public byte[] ColorData { get; private set; }
public Bitmap DepthImg1Bitmap { get; private set; }
public bool frameReceived = false;
public bool captureThread1bool = true;
public bool TakeImage;
public int i = 0;
public Thread uiThread; // UI 线程
public CancellationTokenSource tokenSource = new CancellationTokenSource();
public delegate void NewFrameEventHandler(VideoFrame colorFrame, DepthFrame depthFrame);
public event NewFrameEventHandler NewFrameArrived;
public event NewFrameEventHandler NewFrameArrived11;
/// <summary>
/// 初始化設定並連接RealSenseL515相機
/// </summary>
internal void Connect()
{
var ctx = new Context();
// 查找L515设备
var devices = ctx.QueryDevices();
var adev = devices.FirstOrDefault(d => d.Info[CameraInfo.Name] == "Intel RealSense L515");
if (adev == null)
{
throw new Exception("No RealSense L515 detected");
}
// 连接成功
Console.WriteLine("Connected to: " + adev.Info[CameraInfo.Name]);
}
/// <summary>
/// 開RealSenseL515相機
/// </summary>
[Obsolete]
internal void OpenCamera() //開啟相機
{
Connect();
var cfg = new Config(); // 通過序列號啟用設備
cfg.EnableStream(Stream.Depth, 640, 480, Format.Z16, 30);
cfg.EnableStream(Stream.Color, 640, 480, Format.Rgb8, 30);
pipeline = new Pipeline(); // 建立管線
var pp = pipeline.Start(cfg);// 啟動管線
// 獲取深度流和彩色流的配置
depthStream = pp.GetStream<VideoStreamProfile>(Stream.Depth);
colorStream = pp.GetStream<VideoStreamProfile>(Stream.Color);
SharedExtrinsics = depthStream.GetExtrinsicsTo(colorStream);// 獲取深度流到彩色流的外部參數 // extrinsics.rotation 是列主要的旋轉矩陣 // extrinsics.translation 是以米為單位的 xyz 位移
//獲取並應用深度到彩色的外部參數
var p1 = SharedExtrinsics.Transform(new Intel.RealSense.Math.Vector());
Console.WriteLine($"將外部參數應用到原點:({p1.x}, {p1.y}, {p1.z})");
InternalAndExternalGinseng();
var sensor = pp.Device.QuerySensors().First(s => s.Is(Extension.DepthSensor)); // 取得深度感測器
var blocks = sensor.ProcessingBlocks.ToList(); // 取得處理區塊
Sensor sensor1 = pp.Device.Sensors[0];
scale = sensor1.DepthScale;
Console.WriteLine("取得深度單位" + scale + "m");
try
{
// 建立自定義處理區塊
block = new CustomProcessingBlock((f, src) =>
{
using (var releaser = new FramesReleaser())
{
// 對每個框架執行處理區塊
foreach (ProcessingBlock p in blocks)
{
f = p.Process(f).DisposeWith(releaser);
// 對齊
f = f.ApplyFilter(align).DisposeWith(releaser);
// 使用 colorizer 處理區塊上色
f = f.ApplyFilter(colorizer).DisposeWith(releaser);
// 取得影格
var frames = f.As<FrameSet>();
var colorFrame = frames[Stream.Color, Format.Rgb8].DisposeWith(releaser);
var depthFrame = frames[Stream.Depth, Format.Z16].DisposeWith(releaser);
var colorizedDepth = frames[Stream.Depth, Format.Rgb8].DisposeWith(releaser);
// 合成影格
var res = src.AllocateCompositeFrame(colorizedDepth, colorFrame, depthFrame);
src.FrameReady(res);
}
}
});
// 啟動自定義區塊
block.Start(f =>
{
// 取得影格
using (var frames = f.As<FrameSet>())
{
// 取得彩色與深度影格
var _colorFrame = frames.ColorFrame.DisposeWith(frames);
var _depthFrame = frames.DepthFrame.DisposeWith(frames); // 原始的 depthFrame
var colorizedDepth = frames.First<VideoFrame>(Stream.Depth, Format.Rgb8).DisposeWith(frames); // 取得彩色化的深度影格
Image<Rgb, byte> emguImage = ColorFrameToImage(_colorFrame); // 轉換成影像格式
bitmap = emguImage.ToBitmap();
var imageDepth = DepthFrameToImage(colorizedDepth); // 轉換成影像格式
DepthBitmap = imageDepth.ToBitmap();// 將 Emgu CV 影像轉換成 Bitmap
// 在 UI 線程上更新 PictureBox
form.Invoke((MethodInvoker)delegate
{
form.SetPictureBox4Image(bitmap);
form.SetPictureBox5Image(DepthBitmap);
});
// 觸發 NewFrameArrived 事件
OnNewFrameArrived(_colorFrame, _depthFrame);
OnNewFrameArrived1(_colorFrame, _depthFrame);
}
});
cts = new CancellationTokenSource();
// 创建并启动数据处理任务,覆盖现有的任务。
dataProcessingTask = System.Threading.Tasks.Task.Factory.StartNew(async () =>
{
while (!cts.Token.IsCancellationRequested)
{
using (var frames = pipeline.WaitForFrames())
{
if (frames != null)
{
block.Process(frames);
frameReceived = true;
}
else
{
frameReceived = false;
}
}
if (!frameReceived) // 如果没有接收到相机的帧数
{
if (form.cameraOpened) // 如果相机已经打开,则关闭它
{
form.cam = false;
form.cameraOpened = false;
await CloseCamera(); // 關閉相機
}
if (!form.cameraOpened) // 如果相机未打开,则打开它
{
form.cam = true;
OpenCamera(); // 打開像機
}
frameReceived = true;
}
}
}, cts.Token);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
private void OnNewFrameArrived(VideoFrame colorFrame, DepthFrame depthFrame)
{
NewFrameEventHandler handler = NewFrameArrived;
handler?.Invoke(colorFrame, depthFrame);
}
private void OnNewFrameArrived1(VideoFrame colorFrame, DepthFrame depthFrame)
{
NewFrameEventHandler handler11 = NewFrameArrived11;
handler11?.Invoke(colorFrame, depthFrame);
}
// 將深度影格轉換為影像
Image<Rgb, byte> DepthFrameToImage(VideoFrame frame)
{
// 轉換並複製影格資料
var image = new Image<Rgb, byte>(frame.Width, frame.Height);
var bytes = new byte[frame.Stride * frame.Height];
System.Runtime.InteropServices.Marshal.Copy(frame.Data, bytes, 0, bytes.Length);
// 指定影像的位元組資料
image.Bytes = bytes;
return image;
}
// 將彩色影格轉換為影像
Image<Rgb, byte> ColorFrameToImage(VideoFrame frame)
{
// 轉換並複製影格資料
var image = new Image<Rgb, byte>(frame.Width, frame.Height);
var bytes = new byte[frame.Stride * frame.Height];
System.Runtime.InteropServices.Marshal.Copy(frame.Data, bytes, 0, bytes.Length);
// 指定影像的位元組資料
image.Bytes = bytes;
return image;
}
/// <summary>
/// 關RealSenseL515相機
/// </summary>
/// <returns></returns>
public async System.Threading.Tasks.Task CloseCamera()
{
if (pipeline != null)
{
CancellationTokenSource localCts = cts; // 避免并发访问
System.Threading.Tasks.Task localDataProcessingTask = dataProcessingTask;
try
{
if (localCts != null && !localCts.IsCancellationRequested)
{
localCts.Cancel(); // 取消任务
}
if (localDataProcessingTask != null)
{
// 等待数据处理任务结束
await System.Threading.Tasks.Task.WhenAll(
localDataProcessingTask,
System.Threading.Tasks.Task.Delay(Timeout.Infinite, localCts.Token)
);
}
}
catch (OperationCanceledException)
{
// 忽略任务取消异常
}
catch (Exception ex)
{
Console.WriteLine("Error during camera close: " + ex.Message);
}
finally
{
if (pipeline != null)
{
pipeline.Stop(); // 停止管线
pipeline.Dispose(); // 释放资源
pipeline = null; // 确保不再引用已关闭的管线
}
}
}
}
/// <summary>
/// 提取金樣本
/// </summary>
[Obsolete]
public void CaptureImagesAndSave() //提取金樣本
{
DigitalSignal digitalSignalDo0 = (DigitalSignal)Form1.controller.IOSystem.GetSignal("Do0");
DigitalSignal digitalSignalDo1 = (DigitalSignal)Form1.controller.IOSystem.GetSignal("Do1");
digitalSignalDo0.Value = 1;
Form1.controller.Rapid.Start();
// 使用一個新的執行緒來運行 while 迴圈
Thread captureThread = new Thread(() =>
{
while (form.ImageProcessing)
{
if (digitalSignalDo1.Value == 1)
{
// 清除舊的資料
coordTextBuilder.Clear();
objectCounter = 1; // 重置物體編號1
Yolo3D();
digitalSignalDo1.Value = 0;
form.ImageProcessing = false;
break;
}
}
});
// 啟動執行緒
captureThread.Start();
}
/// <summary>
/// 提取比對樣本
/// </summary>
[Obsolete]
public void CompareSamples() //提取比對樣本
{
DigitalSignal digitalSignalDo0 = (DigitalSignal)Form1.controller.IOSystem.GetSignal("Do0");
DigitalSignal digitalSignalDo1 = (DigitalSignal)Form1.controller.IOSystem.GetSignal("Do1");
DigitalSignal digitalSignalDo3 = (DigitalSignal)Form1.controller.IOSystem.GetSignal("Do3");
digitalSignalDo1.Value = 0;
digitalSignalDo0.Value = 1;
Form1.controller.Rapid.Start();
ManualResetEvent pauseEvent = new ManualResetEvent(true);
// 使用一個新的執行緒來運行 while 迴圈
Thread captureThread1 = new Thread(() =>
{
while (form.ImageProcessing1)
{
Thread.Sleep(2); // 每 10 毫秒檢查一次
if (digitalSignalDo1.Value == 1)
{
Extract();
digitalSignalDo1.Value = 0;
digitalSignalDo3.Value = 0;
digitalSignalDo0.Value = 0;
form.ImageProcessing1 = false;
// 暫停執行緒
pauseEvent.Reset();
break;
}
}
});
// 啟動執行緒
captureThread1.Start();
// 恢復執行緒
pauseEvent.Set();
}
[Obsolete]
public void Extract()
{
// 訂閱 NewFrameArrived 事件
NewFrameArrived11 += Comparesamples_NewFrameArrived;
}
[Obsolete]
public async void Comparesamples_NewFrameArrived(VideoFrame colorFrame, DepthFrame depthFrame)
{
form.pictureBox6.Image = null;
ConvertDepthToPointCloud(depthFrame);
byte[] colorData1 = new byte[colorFrame.Width * colorFrame.Height * colorFrame.BitsPerPixel];
colorFrame.CopyTo(colorData1);
ushort[] depthData1 = new ushort[depthFrame.Width * depthFrame.Height];
depthFrame.CopyTo(depthData1);
using (VideoFrame depth1 = colorizer.Colorize(depthFrame))
{
Image<Rgb, byte> ColorImg1 = new Image<Rgb, byte>(colorFrame.Width, colorFrame.Height, colorFrame.Stride, colorFrame.Data);
Image<Rgb, byte> DepthImg1 = new Image<Rgb, byte>(depth1.Width, depth1.Height, depth1.Stride, depth1.Data);
//Image<Rgb, byte> DepthImg2 = DepthImg1;
//DepthImg1Bitmap = DepthImg2.ToBitmap();
string colorFileName = $"Compare samples_Color.bmp";
string depthFileName = $"Compare samples_Depth.bmp";
//string YOLO = $"YOLO.bmp";
string colorPath = Path.Combine(folderPath1, colorFileName);
string depthPath = Path.Combine(folderPath1, depthFileName);
ColorImg1.Save(colorPath);
form.pictureBox6.Image = ColorImg1.Bitmap;
DepthImg1.Save(depthPath);
}
// 取得當前日期和時間
DateTime now = DateTime.Now;
if (deepestZ != float.MinValue && deepestZ - shallowestZ > (GolddeepestZ - GoldshallowestZ) / 2.5 && i > percentageInsideCrop && deepestZ - shallowestZ < GolddeepestZ - GoldshallowestZ + 2)
{
NewFrameArrived11 -= Comparesamples_NewFrameArrived;
// 以日期時間為名稱建立資料夾
folderPath1 = $"C:\\c#\\cam\\比對樣本{now:yyyy_MM_dd_HH_mm_ss}";
form.Home();
await CalculateDisplacement();
}
else
{
// 以日期時間為名稱建立資料夾
folderPath1 = $"C:\\c#\\cam\\比對樣本超過範圍{now:yyyy_MM_dd_HH_mm_ss}";
if (form.checkBox1.Checked)
{
MessageBox.Show("超過離鏡頭允許範圍,請確認物件擺放位置\n 距離誤超過樣本位置3公分\n 確保物件在ROI範圍內");
NewFrameArrived11 -= Comparesamples_NewFrameArrived;
form.button30.Enabled = true;
form.ok1 = true;
}
}
// 检查文件夹是否存在,如果不存在则创建
if (!Directory.Exists(folderPath1))
{
Directory.CreateDirectory(folderPath1);
}
}
/// <summary>
/// YOLO辨識
/// </summary>
public System.Drawing.Image ProcessImage(System.Drawing.Image colorBitmap) //YOLOV5-ONNX
{
yoloFeaturePoints.Clear(); // 清空上次的特征点
// 先複製原始圖片
var image = new Bitmap(colorBitmap);
var scorer = new YoloScorer<YoloCocoP5Model>("C:/c#/20231102/exp/weights/best.onnx");
List<YoloPrediction> predictions = scorer.Predict(image);
var graphics = Graphics.FromImage(image);
// 按照 centerX + centerY 的大小排序 predictions
predictions = predictions.OrderBy(prediction => prediction.Rectangle.Left + prediction.Rectangle.Width / 2 + prediction.Rectangle.Top + prediction.Rectangle.Height / 2).ToList();
foreach (var prediction in predictions)
{
double score = Math.Round(prediction.Score, 2);
graphics.DrawRectangles(new System.Drawing.Pen(prediction.Label.Color, prediction.Label.PenWidth), new[] { prediction.Rectangle });
var (x, y) = (prediction.Rectangle.X - 3, prediction.Rectangle.Y - 37);
// 計算方寬中心點和方寬寬高
float centerX = prediction.Rectangle.Left + prediction.Rectangle.Width / 2;
float centerY = prediction.Rectangle.Top + prediction.Rectangle.Height / 2;
float width = prediction.Rectangle.Width;
float height = prediction.Rectangle.Height;
// 將物體編號、物體名稱和信心值一起顯示在標籤中
var labelWithScore = $"{objectCounter}"; //{prediction.Label.Name}
graphics.DrawString(labelWithScore,
new Font("Times New Roman", 20, GraphicsUnit.Pixel), new SolidBrush(prediction.Label.Color),
new PointF(x, y));
Item = new ListViewItem(new[]
{
objectCounter.ToString(), // 編號
prediction.Label.Name.ToString(),// 物體名稱
score.ToString(), // 信心值
(centerX,centerY).ToString(),// 方寬中心點
(height,width).ToString() // 方寬寬高
});
form.listView4.Items.Add(Item);
objectCounter++; // 增加物體编號
if (prediction.Label.Name == "solder joint") //篩選特徵點
{
yoloFeaturePoints.Add(new System.Drawing.PointF(centerX, centerY));// 將YOLO預測的特徵點座標添加到列表
}
}
form.pictureBox1.Image = image;
return new Bitmap(image); // 返回新的 Bitmap
}
/// <summary>
/// 儲存彩色和深度影像和YOLO影像
/// </summary>
/// <param name="colorImage"></param>
/// <param name="depthImage"></param>
/// <param name="yoloImage"></param>
private void SaveImages()// 儲存金樣本彩色和深度影像和YOLO影像
{
folderPath = "C:\\c#\\cam\\金樣本";
// 检查文件夹是否存在,如果不存在则创建
if (!Directory.Exists(folderPath))
{
Directory.CreateDirectory(folderPath);
}
// 在這裡加入儲存影像的程式碼,可以使用 Bitmap.Save 方法或其他方式進行儲存
string yoloName = $"Gold Standard_YoLo.bmp";
string yoloPath = Path.Combine(folderPath, yoloName);
yoloimage.Save(yoloPath);
// 釋放 yoloImage
yoloimage.Dispose();
string colorFileName = $"Gold Standard_Color.bmp";
string depthFileName = $"Gold Standard_Depth.bmp";
string colorPath = Path.Combine(folderPath, colorFileName);
string depthPath = Path.Combine(folderPath, depthFileName);
ColorImg.Save(colorPath);
DepthImg.Save(depthPath);
Console.WriteLine("影像已儲存");
}
#region
/// <summary>
/// $"YOLO特徵點({u}, {v})的三維座標: X={X}, Y={Y}, Z={Z}"
/// </summary>
[Obsolete]
public void Yolo3D()
{
storedCoordinates.Clear();
ColorData = null;
DepthData = null;
// 初始化彩色圖像和深度圖像
ColorImg = null;
DepthImg = null;
// 訂閱 NewFrameArrived 事件
NewFrameArrived += Yolo3D_NewFrameArrived;
}
[Obsolete]
private void Yolo3D_NewFrameArrived(VideoFrame colorFrame, DepthFrame depthFrame)
{
// 有興趣範圍
CroppingRect = new Rectangle(form.left, form.top, form.right - form.left, form.bottom - form.top);
ColorData = new byte[colorFrame.Width * colorFrame.Height * colorFrame.BitsPerPixel];
colorFrame.CopyTo(ColorData);
DepthData = new ushort[depthFrame.Width * depthFrame.Height];
depthFrame.CopyTo(DepthData);
using (VideoFrame depth = colorizer.Colorize(depthFrame))
{
ColorImg = new Image<Rgb, byte>(colorFrame.Width, colorFrame.Height, colorFrame.Stride, colorFrame.Data);
DepthImg = new Image<Rgb, byte>(depth.Width, depth.Height, depth.Stride, depth.Data);
Image<Rgb, byte> DepthImg1 = DepthImg;
DepthImg1Bitmap = DepthImg1.ToBitmap();
}
// 確保彩色圖丟進 YOLO 出來的特徵點是跟深度影像點位一樣的
yoloimage = (Bitmap)ProcessImage(ColorImg.Bitmap); // 丟入 YOLO
Bitmap yolo = (Bitmap)yoloimage.Clone(); // 使用 Clone 方法進行複製
using (Graphics g = Graphics.FromImage(DepthImg1Bitmap))
{
// 假設 deph 是包含深度值的集合
List<float> deph2 = new List<float>();
// 使用 yoloFeaturePoints 進行處理
foreach (System.Drawing.PointF featurePoint in yoloFeaturePoints)
{
// 假设 maxDepth 是您深度图的最大深度值
//float maxDepth = 100.0f;
//featurePoint 為彩色YOLO過的特徵點座標
int u = (int)featurePoint.X;
int v = (int)featurePoint.Y;
// 在深度图上对应像素的坐标
int depthPixelX = (int)u;
int depthPixelY = (int)v;
Z = depthFrame.GetDistance(depthPixelX, depthPixelY);
// 將深度值加入到 Z 集合中
deph2.Add(Z);
//獲取影像相機座標系
cameraPoint = Compute3DCoordinate(depthPixelX, depthPixelY, Z);
MathNet.Numerics.LinearAlgebra.Vector<double> cameraPointVector = MathNet.Numerics.LinearAlgebra.Vector<double>.Build.Dense(new double[] { cameraPoint.X, cameraPoint.Y, cameraPoint.Z });
cameraPointsList.Add(cameraPointVector);
// 输出深度值
Console.WriteLine($"YOLO特徵點 ({depthPixelX}, {depthPixelY}): {Z * 100} cm");
coordTextBuilder.AppendLine($"YOLO特徵點({u}, {v}): 深度值: {Z * 100} cm");
form.label16.Text = coordTextBuilder.ToString();
Console.WriteLine($"相機座標系: ({cameraPoint.X * 1000}, {cameraPoint.Y * 1000}, {cameraPoint.Z * 1000}) mm");
System.Numerics.Vector3 currentCoordinate = new System.Numerics.Vector3(cameraPoint.X * 1000, cameraPoint.Y * 1000, cameraPoint.Z * 1000);
storedCoordinates.Add(currentCoordinate);
}
// 找出 Z 集合中的最大值
MaxDepthValue = deph2.Max();
Console.WriteLine($"maxDepthValue {MaxDepthValue} cm");
form.label21.Text = "待焊物離鏡頭距離不超過" + (MaxDepthValue * 100 + 3) + "cm";
}
using (Graphics g = Graphics.FromImage(DepthImg1Bitmap))
{
// 使用 yoloFeaturePoints 進行處理
foreach (System.Drawing.PointF featurePoint in yoloFeaturePoints)
{
// 假设 maxDepth 是您深度图的最大深度值
float maxDepth = 100.0f;
//featurePoint 為彩色YOLO過的特徵點座標
float u = featurePoint.X;
float v = featurePoint.Y;
// 在深度图上对应像素的坐标
int depthPixelX = (int)u;
int depthPixelY = (int)v;
// 在畫布上繪製特徵中心點
float depthPointRadius = 10.0f;
g.FillEllipse(System.Drawing.Brushes.Red, Convert.ToSingle(u - depthPointRadius), Convert.ToSingle(v - depthPointRadius), Convert.ToSingle(depthPointRadius * 2), Convert.ToSingle(depthPointRadius * 2));
float rectSize = 5.0f;
// 将深度值映射到颜色值(这里是简单的映射示例,具体映射关系可能需要根据实际情况调整)
byte mappedDepthColor = (byte)(Z / maxDepth * 255);
g.FillEllipse(new SolidBrush(System.Drawing.Color.FromArgb(mappedDepthColor, mappedDepthColor, mappedDepthColor)), depthPixelX - rectSize, depthPixelY - rectSize, 2 * rectSize, 2 * rectSize);
}
}
using (Graphics g = Graphics.FromImage(yolo))
{
// 使用 yoloFeaturePoints 進行處理
foreach (System.Drawing.PointF featurePoint in yoloFeaturePoints)
{
// 假设 maxDepth 是您深度图的最大深度值
float maxDepth = 100.0f;
//featurePoint 為彩色YOLO過的特徵點座標
float u = featurePoint.X;
float v = featurePoint.Y;
// 在深度图上对应像素的坐标
int depthPixelX = (int)u;
int depthPixelY = (int)v;
// 在畫布上繪製特徵中心點
float depthPointRadius = 10.0f;
g.FillEllipse(System.Drawing.Brushes.Red, Convert.ToSingle(u - depthPointRadius), Convert.ToSingle(v - depthPointRadius), Convert.ToSingle(depthPointRadius * 2), Convert.ToSingle(depthPointRadius * 2));
// 在pictureBox1上绘制矩形表示深度图上的颜色值
float rectSize = 5.0f;
// 将深度值映射到颜色值(这里是简单的映射示例,具体映射关系可能需要根据实际情况调整)
byte mappedDepthColor = (byte)(Z / maxDepth * 255);
// 在pictureBox1上绘制矩形表示深度图上的颜色值
g.FillEllipse(new SolidBrush(System.Drawing.Color.FromArgb(mappedDepthColor, mappedDepthColor, mappedDepthColor)), depthPixelX - rectSize, depthPixelY - rectSize, 2 * rectSize, 2 * rectSize);
}
}
form.groupBox2.Enabled = true;
form.pictureBox1.Image = yolo;
form.pictureBox2.Image = ColorImg.Bitmap;
form.pictureBox3.Image = DepthImg1Bitmap;
form.pictureBox3.Refresh();
form.pictureBox3.Refresh();
form.PlanningAOI();
// 檢查是否滿足條件,如果沒有,就等待直到滿足
while (!form.ROI)
{
Thread.Sleep(10); // 每 10 毫秒檢查一次
if (form.ImageProcessing == true)
{
break;
}
}
form.goldsample = true;
//建立點雲
ConvertDepthToPointCloud(depthFrame);
form.goldsample = false;
SaveImages();
TakeImage = true;
NewFrameArrived -= Yolo3D_NewFrameArrived;
}
//建立點雲
public void ConvertDepthToPointCloud(DepthFrame depthFrame)
{
i = 0;
threshold = MaxDepthValue * 1000 + 40;
// 初始化追蹤最深點的座標和深度值
deepestZ = float.MinValue;
deepestX = 0;
deepestY = 0;
// 初始化追蹤最淺點的座標和深度值
shallowestZ = float.MaxValue;
shallowestX = 0;
shallowestY = 0;
// 初始化追蹤 y 軸最小值和最大值
float yMin = float.MaxValue;
float yMax = float.MinValue;
// 建立一個StringBuilder來儲存.pcd檔的內容
StringBuilder pcdData = new StringBuilder();
// 初始化一個列表來儲存點雲數據
List<string> pointCloudData = new List<string>();
// 取得影像中間的y座標值
// 遍歷深度數據的每一個元素
for (int y = 0; y < depthFrame.Height; y++)
{
for (int x = 0; x < depthFrame.Width; x++)
{
// 檢查點是否在裁剪範圍內
if (x >= form.left && x <= form.right && y >= form.top && y <= form.bottom)
{
// 將深度數據轉換為點雲數據
float Z2 = (float)depthFrame.GetDistance(x, y);
Z2 *= 1000;
if (Z2 < threshold && Z2 != 0)
{
float xx = (float)((x - colorIntrinsics.ppx) * Z2 / colorIntrinsics.fx);
float yy = (depthFrame.Height - y - colorIntrinsics.ppy) * Z2 / colorIntrinsics.fy; // 翻轉y軸的座標
float z = Z2;
// 更新 y 軸的最小值和最大值
if (yy < yMin) yMin = yy;
if (yy > yMax) yMax = yy;
// 將點雲數據儲存到列表中
pointCloudData.Add($"{xx} {yy} {z}");
i++;
// 根據 y 軸的範圍來定義矩形框的大小
float boxSize = (yMax - yMin) / 5; // 以五行為主
float boxMin = yMin;
float boxMax = boxMin + boxSize;
// 在定義的矩形框中找到最淺的點和最深的點
if (yy >= boxMin && yy <= boxMax)
{
// 更新最深點的座標和深度值
if (Z2 > deepestZ)
{
deepestZ = Z2;
deepestX = (int)xx;
deepestY = (int)yy;
}
// 更新最淺點的座標和深度值
if (Z2 < shallowestZ)
{
shallowestZ = Z2;
shallowestX = (int)xx;
shallowestY = (int)yy;
}
}
}
}
}
}
// 最深點的座標和深度值
Console.WriteLine($"深度限制: {threshold}");
Console.WriteLine($"最深點: X={deepestX}, Y={deepestY}, Z={deepestZ}");
Console.WriteLine($"最淺點: X={shallowestX}, Y={shallowestY}, Z={shallowestZ}");
// 寫入.pcd檔的頭部資訊
pcdData.AppendLine("VERSION .7");
pcdData.AppendLine("FIELDS x y z");
pcdData.AppendLine("SIZE 4 4 4");
pcdData.AppendLine("TYPE F F F");
pcdData.AppendLine("COUNT 1 1 1");
pcdData.AppendLine("WIDTH " + i);
pcdData.AppendLine("HEIGHT " + 1);
pcdData.AppendLine("VIEWPOINT 0 0 0 1 0 0 0");
pcdData.AppendLine("POINTS " + i);
pcdData.AppendLine("DATA ascii");
// 寫入.pcd檔時從列表中取出點雲數據並寫入.pcd檔
foreach (string point in pointCloudData)
{
pcdData.AppendLine(point);
}
if (form.goldsample == true)
{
Goldsamplepointcloudnumber = i;
// 計算在裁剪範圍內的點的百分比
percentageInsideCrop = Goldsamplepointcloudnumber * 0.7;
GolddeepestZ = deepestZ;
GoldshallowestZ = shallowestZ;
Console.WriteLine($"金樣本點雲數量 {Goldsamplepointcloudnumber}");
// 指定儲存.pcd檔的資料夾
folderPath = "C:\\c#\\cam\\金樣本";
// 指定.pcd檔的檔名
fileName = "Gold Standard.pcd";
// 檢查資料夾是否存在,如果不存在,則建立該資料夾
if (!Directory.Exists(folderPath))
{
Directory.CreateDirectory(folderPath);
}
// 檔案將被儲存到指定的資料夾
File.WriteAllText(Path.Combine(folderPath, fileName), pcdData.ToString());
}
else
{
Console.WriteLine($"比對樣本點雲數量 {i}");
// 取得當前日期和時間
DateTime now = DateTime.Now;
if (deepestZ != float.MinValue && deepestZ - shallowestZ > (GolddeepestZ - GoldshallowestZ) / 2.5 && i > percentageInsideCrop && deepestZ - shallowestZ < GolddeepestZ - GoldshallowestZ + 2)
{
// 以日期時間為名稱建立資料夾
folderPath1 = $"C:\\c#\\cam\\比對樣本{now:yyyy_MM_dd_HH_mm_ss}";
}
else
{
// 以日期時間為名稱建立資料夾
folderPath1 = $"C:\\c#\\cam\\比對樣本超過範圍{now:yyyy_MM_dd_HH_mm_ss}";
}
// 指定.pcd檔的檔名
fileName1 = "Compare samples.pcd";
// 檢查資料夾是否存在,如果不存在,則建立該資料夾
if (!Directory.Exists(folderPath1))
{
Directory.CreateDirectory(folderPath1);
}
File.WriteAllText(Path.Combine(folderPath1, fileName1), pcdData.ToString());
}
}
/// <summary>
/// 相機座標系中的像素座標轉換成相機座標系中的三維空間座標
/// </summary>
/// <param name="u"></param>
/// <param name="v"></param>
/// <param name="Z"></param>
/// <param name="dist">DepthFrame在座標(x,y)的深度值(單位m)</param>
/// <returns></returns>
public System.Numerics.Vector3 Compute3DCoordinate(double x, double y, double Z)
{
X = (float)((x - colorIntrinsics.ppx) * Z / colorIntrinsics.fx);
Y = (float)((y - colorIntrinsics.ppy) * Z / colorIntrinsics.fy);
// 畸變模型
if (colorIntrinsics.model == Distortion.ModifiedBrownConrady)
{
float r2 = (float)(X * X + Y * Y);
float f = 1 + colorIntrinsics.coeffs[0] * r2 + colorIntrinsics.coeffs[1] * r2 * r2 + colorIntrinsics.coeffs[4] * r2 * r2 * r2;
float ux = (float)(X * f + 2 * colorIntrinsics.coeffs[2] * X * Y + colorIntrinsics.coeffs[3] * (r2 + 2 * X * X));
float uy = (float)(Y * f + 2 * colorIntrinsics.coeffs[3] * X * Y + colorIntrinsics.coeffs[2] * (r2 + 2 * Y * Y));
X = ux;
Y = uy;
}
Console.WriteLine($"Compute3DCoordinate: {X} m ,{Y} m ,{Z} m");
return new System.Numerics.Vector3((float)X, (float)Y, (float)Z);
}
/// <summary>
/// 相機內外參參數
/// </summary>
internal void InternalAndExternalGinseng()//內參出輸
{
// 打印深度流和彩色流的内参信息
depthIntrinsics = depthStream.GetIntrinsics();
depthExtrinsics = depthStream.GetExtrinsicsTo(colorStream);
colorIntrinsics = colorStream.GetIntrinsics();
colorExtrinsics = colorStream.GetExtrinsicsTo(depthStream);
Console.WriteLine("深度相機内参:");
Console.WriteLine("寬度: " + depthIntrinsics.width);// 深度相機寬度
Console.WriteLine("高度: " + depthIntrinsics.height);// 深度相機高度
Console.WriteLine("Fx: " + depthIntrinsics.fx);// 深度相機的水平焦距
Console.WriteLine("Fy: " + depthIntrinsics.fy);// 深度相機的垂直焦距
Console.WriteLine("Cx: " + depthIntrinsics.ppx);// 深度相機的主點在水平方向上的坐標
Console.WriteLine("Cy: " + depthIntrinsics.ppy);// 深度相機的主點在垂直方向上的坐標
Console.WriteLine("模型: " + depthIntrinsics.model);// 深度相機的内參模型
Console.WriteLine("彩色相機内参:");
Console.WriteLine("寬度: " + colorIntrinsics.width);
Console.WriteLine("高度: " + colorIntrinsics.height);
Console.WriteLine("Fx: " + colorIntrinsics.fx);
Console.WriteLine("Fy: " + colorIntrinsics.fy);
Console.WriteLine("Cx: " + colorIntrinsics.ppx);
Console.WriteLine("Cy: " + colorIntrinsics.ppy);
Console.WriteLine("模型: " + colorIntrinsics.model);
}
#endregion
/// <summary>
/// 計算位移量
/// </summary>
[Obsolete]
public async Task CalculateDisplacement()
{
await Task.Run(async () =>
{
using (Process process = new Process())
{
form.cam = false;
form.cameraOpened = false;
form.label17.Text = "相機狀態:關";
form.button27.Text = "開相機";
form.label17.BackColor = Color.Transparent;
await CloseCamera(); // 關閉相機
process.StartInfo.FileName = "C:\\Users\\asasa\\Desktop\\PCLdemo\\build\\Debug\\PCLdemo.exe"; // C++程式的路徑
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.Start();
// 讀取C++程式的輸出
string output = process.StandardOutput.ReadToEnd();
process.WaitForExit();
// 顯示C++程式的完整輸出
Console.WriteLine("C++程式的輸出為:");
Console.WriteLine(output);
// 指定輸出文件的路徑
string outputFilePath = "C:\\c#\\cam\\output.txt";
// 讀取輸出文件的整個內容
string fileContent = File.ReadAllText(outputFilePath);
// 將文件內容按逗號分割成字符串數組
string[] values = fileContent.Split(',');
// 檢查值的數量是否正確
if (values.Length == 6)
{
// 提取並使用每個值
if (float.TryParse(values[0], out x) &&
float.TryParse(values[1], out y) &&
float.TryParse(values[2], out z) &&
float.TryParse(values[3], out Rx) &&
float.TryParse(values[4], out Ry) &&
float.TryParse(values[5], out Rz))
{
// 显示提取到的值
Console.WriteLine($"x: {x}, y: {y}, z: {z}, Rx: {Rx}, Ry: {Ry}, Rz: {Rz}");
if (x != 0)
{
// 嘗試關閉 C++ 進程
if (!process.HasExited)
{
process.Kill(); // 強制關閉
}
form.Pathchange();
}
}
else
{
Console.WriteLine("無法解析一個或多個值。");
}
}
else
{
Console.WriteLine("輸出格式無效。");
}
}
});
}
}
}