DX2.0缩略图类class_image.php

本文介绍了一种图片处理库的选择及其配置方法,包括GD和ImageMagick的对比,并详细解释了如何设置图片缩略图的质量及水印的添加条件、类型、融合度和质量等参数。
/** 
'imagelib'       
图片处理库类型(GD=>0,ImageMagick=>1) GD 是最广泛的处理库但是使用的系统资源较多。ImageMagick 速度快系统资源占用少,但需要服务器有执行命令行命令的权限。如果你的服务器有条件安装此程序 
'imageimpath'        
ImageMagick 6 程序的安装路径。如果服务器的操作系统为 Windows,路径不要使用长文件名 
'thumbquality'   
缩略图质量0 ~ 100 
'watermarkstatus' 
是否启用水印 
'watermarkminwidth' 
水印添加条件:wxh 设置水印添加的条件,小于此尺寸的图片附件将不添加水印 
'watermarkminheight'     
水印添加条件:wxh 设置水印添加的条件,小于此尺寸的图片附件将不添加水印 
'watermarktype'  
类型水印: gif png text 
'watermarktext'  
文本水印文字 
'watermarktrans' 
水印融合度:  设置 GIF 类型水印图片与原始图片的融合度,范围为 1~100 的整数,数值越大水印图片透明度越低。PNG 类型水印本身具有真彩透明效果,无须此设置。本功能需要开启水印功能后才有效 
'watermarkquality' 
JPEG 水印质量 : 设置 JPEG 类型的图片附件添加水印后的质量参数,范围为 0~100 的整数,数值越大结果图片效果越好,但尺寸也越大。本功能需要开启水印功能后才有效 
 
*/  
function image() {  
    global $_G;  
    $s = &$_G['setting'];  
    $this->param = array(  
        'imagelib'      => $s['imagelib'],  
        'imageimpath'       => $s['imageimpath'],  
        'thumbquality'      => $s['thumbquality'],  
        'watermarkstatus'   => unserialize($s['watermarkstatus']),  
        'watermarkminwidth' => unserialize($s['watermarkminwidth']),  
        'watermarkminheight'    => unserialize($s['watermarkminheight']),  
        'watermarktype'     => $s['watermarktype'],  
        'watermarktext'     => $s['watermarktext'],  
        'watermarktrans'    => unserialize($s['watermarktrans']),  
        'watermarkquality'  => unserialize($s['watermarkquality']),  
    );  
} 

    /** 
     * 生成图片的缩略图 
     * @param $source 图片源路径 
     * @param $target 生成的缩略图路径,路径为相对 data/attachment/ 的文件名 
     *    本地图片省略时自动加后缀 .thumb.jpg,远程图片无法省略 
     * @param $thumbwidth 缩略宽度 
     * @param $thumbheight 缩略高度 
     * @param $thumbtype 缩略方法 
     *    空 : 将原图片上传 
     *    fixnone / 1 : 小于指定大小、保持比率(默认) 
     *    fixwr / 2 : 与指定大小相同、保持比率,超出部分剪切 
     * @param $nosuffix 缩略图路径不加 .thumb.jpg 后缀 
     * @return 是否处理完毕 
     */  
    function Thumb($source, $target, $thumbwidth, $thumbheight, $thumbtype = 1, $nosuffix = 0)  

    /** 
     * 生成图片的水印 
     * @param $source 图片源路径 
     * @param $target 生成的图片路径,省略表示同 $source 
     * @param $type forum - 论坛; portal - 门户; album - 空间相册 
     * @return 是否处理完毕 
     */  
    function Watermark($source, $target = '', $type = 'forum')  

    function error() {} 返回值说明  
    /* 
     * $this->error() 返回值(用于处理失败时) 
     *     0: 图片不符合处理条件,无需处理正常退出 
     *    -1: $source 为无效的图片文件 
     *    -2: 文件权限不足无法处理图片($source 图片无法读取、$target 路径不可写) 
     *    -3: 系统设置错误无法处理图片 
     *    -4: 服务器缺少处理图片所需的功能 
     */  

using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Drawing; using System.Drawing.Drawing2D; using System.IO; using System.Linq; using System.Reflection; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; using System.Xml; using Newtonsoft.Json; namespace Vision.Controls { #region 基础接口定义 public interface IDrawable { Rectangle Bounds { get; } void Draw(Graphics g); event EventHandler StyleChanged; } public interface IPlugin { string Name { get; } VisualElement CreateElement(); } public interface ICommand { void Execute(); void Undo(); } public interface ISerializer { string Format { get; } string Serialize(WorkflowCanvas canvas); void Deserialize(WorkflowCanvas canvas, string data); } #endregion #region 核心实体 public class Port { public string Name { get; set; } public PortType Type { get; set; } public Point Position { get; set; } // 相对于节点的偏移 public Port ConnectedPort { get; set; } // 连接的目标端口 public VisualElement Parent { get; set; } // 所属元素 public Point ScreenPosition { get { // 确保 Parent 已初始化,且 Bounds 有效 if (Parent == null) return Position; return new Point( Parent.Bounds.X + Position.X, // 用 Bounds(逻辑坐标)替代 Location Parent.Bounds.Y + Position.Y ); } } } public enum PortType { Input, Output } public abstract class VisualElement : UserControl, IDrawable { public virtual Rectangle Bounds { get { scale = WorkflowCanvas.Instance.Scale; return new Rectangle( (int)(LogicLocation.X * scale), (int)(LogicLocation.Y * scale), (int)(LogicSize.Width * scale), (int)(LogicSize.Height * scale) ); } } public abstract void Draw(Graphics g); public event EventHandler StyleChanged; public virtual string Title { get; set; } = "DefaultTitle"; protected void OnStyleChanged() => StyleChanged?.Invoke(this, EventArgs.Empty); public bool IsSelected { get; set; } = false; public Point LogicLocation { get; set; } = new Point(0, 0); public Size LogicSize { get; set; } = new Size(100, 50); public float scale {get; set; } // 新增:Z轴层级(值越大越靠前) public int ZIndex { get; set; } = 0; [Browsable(false)] public new Point Location { get => LogicLocation; set => LogicLocation = value; } [Browsable(false)] public new Size Size { get => LogicSize; set => LogicSize = value; } } public class Connection : IDrawable { public Port StartPort { get; set; } public Port EndPort { get; set; } public Point StartPosition { get; set; } // 临时连接起点 public Point EndPosition { get; set; } // 临时连接终点 public Rectangle Bounds { get { if (StartPort != null && EndPort != null) { var minX = Math.Min(StartPort.ScreenPosition.X, EndPort.ScreenPosition.X); var maxX = Math.Max(StartPort.ScreenPosition.X, EndPort.ScreenPosition.X); var minY = Math.Min(StartPort.ScreenPosition.Y, EndPort.ScreenPosition.Y); var maxY = Math.Max(StartPort.ScreenPosition.Y, EndPort.ScreenPosition.Y); return new Rectangle(minX - 10, minY - 10, maxX - minX + 20, maxY - minY + 20); } return new Rectangle(StartPosition.X, StartPosition.Y, EndPosition.X - StartPosition.X, EndPosition.Y - StartPosition.Y); } } public void Draw(Graphics g) { var scale = WorkflowCanvas.Instance.Scale; g.ScaleTransform(scale, scale); // 应用画布缩放 Point start = StartPort?.ScreenPosition ?? StartPosition; Point end = EndPort?.ScreenPosition ?? EndPosition; g.DrawLine(Pens.Black, start, end); // 绘制箭头(末端) DrawArrow(g, end, start); } private void DrawArrow(Graphics g, Point end, Point start) { // 计算箭头方向(从 start 到 end 的向量) float dx = end.X - start.X; float dy = end.Y - start.Y; float angle = (float)Math.Atan2(dy, dx); int arrowSize = (int)(10 * WorkflowCanvas.Instance.Scale); // 箭头大小适配缩放 // 计算箭头的两个侧点 Point arrowPoint1 = new Point( (int)(end.X - arrowSize * Math.Cos(angle - Math.PI / 6)), (int)(end.Y - arrowSize * Math.Sin(angle - Math.PI / 6)) ); Point arrowPoint2 = new Point( (int)(end.X - arrowSize * Math.Cos(angle + Math.PI / 6)), (int)(end.Y - arrowSize * Math.Sin(angle + Math.PI / 6)) ); // 绘制箭头(填充三角形) g.FillPolygon(Brushes.Black, new Point[] { end, arrowPoint1, arrowPoint2 }); } public event EventHandler StyleChanged; } public partial class VisionNode : VisualElement { public string Title { get; set; } = "Node"; public List<Port> InputPorts { get; } = new List<Port>(); public List<Port> OutputPorts { get; } = new List<Port>(); public IDictionary<string, object> Parameters { get; set; } = new Dictionary<string, object>(); public bool IsSelected { get; set; } public override Rectangle Bounds => new Rectangle(Location, Size); public VisionNode() { DoubleBuffered = true; Size = new Size(100, 50); BackColor = Color.Transparent; InputPorts.Add(new Port { Name = "In1", Type = PortType.Input, Position = new Point(Size.Width/2, this.Location.Y), Parent = this }); OutputPorts.Add(new Port { Name = "Out1", Type = PortType.Output, Position = new Point(Size.Width / 2,this.Location.Y+ Size.Height), Parent = this }); } public override void Draw(Graphics g) { // 绘制节点框等其他内容(可再处理缩放) var pen = StyleManager.Instance.NodeBorderPen; g.DrawRectangle(pen, Bounds); // 若有全局缩放,先重置变换(避免文字被缩放影响) g.ResetTransform(); // 保存原始 Graphics 状态 var state = g.Save(); var font = StyleManager.Instance.NodeFont; // 用物理坐标绘制文字(假设 _scale 是画布缩放因子) SizeF textSize = g.MeasureString(Title, font); var physicalX = Bounds.X + (Bounds.Width - textSize.Width) / 2; var physicalY = Bounds.Y + (Bounds.Height - textSize.Height) / 2; g.DrawString(Title, font, Brushes.Black, new PointF(physicalX, physicalY)); g.Restore(state); } protected override void OnLocationChanged(EventArgs e) { base.OnLocationChanged(e); OnStyleChanged(); } } #endregion #region 性能优化模块 public class QuadTree { private readonly Rectangle _bounds; private List<IDrawable> _objects = new List<IDrawable>(); private QuadTree[] _children; private const int MAX_OBJECTS = 10; private const int MIN_SIZE = 100; public QuadTree(Rectangle bounds) => _bounds = bounds; public Rectangle Bounds => _bounds; public void Insert(IDrawable obj) { if (_children != null) { int index = GetChildIndex(obj.Bounds); if (index != -1) { _children[index].Insert(obj); return; } } _objects.Add(obj); if (_objects.Count > MAX_OBJECTS && _bounds.Width > MIN_SIZE) { Subdivide(); var objs = new List<IDrawable>(_objects); _objects.Clear(); foreach (var o in objs) { int index = GetChildIndex(o.Bounds); if (index != -1) _children[index].Insert(o); else _objects.Add(o); } } } private void Subdivide() { int x = _bounds.X, y = _bounds.Y; int w = _bounds.Width / 2, h = _bounds.Height / 2; _children = new QuadTree[4] { new QuadTree(new Rectangle(x, y, w, h)), new QuadTree(new Rectangle(x + w, y, w, h)), new QuadTree(new Rectangle(x, y + h, w, h)), new QuadTree(new Rectangle(x + w, y + h, w, h)) }; } private int GetChildIndex(Rectangle bounds) { if (_children == null) return -1; int midX = _bounds.X + _bounds.Width / 2; int midY = _bounds.Y + _bounds.Height / 2; bool top = bounds.Y < midY; bool bottom = bounds.Bottom > midY; bool left = bounds.X < midX; bool right = bounds.Right > midX; if (top && left) return 0; if (top && right) return 1; if (bottom && left) return 2; if (bottom && right) return 3; return -1; } public void Clear() { _objects.Clear(); if (_children != null) { for (int i = 0; i < _children.Length; i++) { _children[i].Clear(); _children[i] = null; } _children = null; } } public List<IDrawable> Query(Rectangle area) { var result = new List<IDrawable>(); if (!_bounds.IntersectsWith(area)) return result; result.AddRange(_objects.Where(o => o.Bounds.IntersectsWith(area))); if (_children != null) { foreach (var child in _children) result.AddRange(child.Query(area)); } return result; } public void Query(List<IDrawable> result, Rectangle area) { if (!_bounds.IntersectsWith(area)) return; result.AddRange(_objects.Where(o => o.Bounds.IntersectsWith(area))); if (_children != null) { foreach (var child in _children) { child.Query(result, area); } } } } // 增强 ForceDirectedLayout public class ForceDirectedLayout { private const float Repulsion = 10000f; // 排斥力强度 private const float Attraction = 0.1f; // 吸引力强度 private const float Damping = 0.9f; // 阻尼系数 private const int MaxIterations = 50; // 最大迭代次数 // 存储节点速度 private Dictionary<VisualElement, PointF> _velocities = new Dictionary<VisualElement, PointF>(); public void Layout(IEnumerable<VisualElement> nodes, IEnumerable<Connection> connections) { var nodeList = nodes.ToList(); InitializeVelocities(nodeList); // 迭代计算力和位置 for (int i = 0; i < MaxIterations; i++) { ApplyForces(nodeList, connections); UpdatePositions(nodeList); } } private void InitializeVelocities(List<VisualElement> nodes) { _velocities.Clear(); foreach (var node in nodes) { _velocities[node] = new PointF(0, 0); } } private void ApplyForces(List<VisualElement> nodes, IEnumerable<Connection> connections) { // 计算节点间排斥力 foreach (var node in nodes) { foreach (var other in nodes.Where(n => n != node)) { float dx = node.LogicLocation.X - other.LogicLocation.X; float dy = node.LogicLocation.Y - other.LogicLocation.Y; float distance = (float)Math.Sqrt(dx * dx + dy * dy); if (distance < 1) distance = 1; // 避免除零 float force = Repulsion / (distance * distance); // 更新速度 _velocities[node] = new PointF( _velocities[node].X + dx / distance * force, _velocities[node].Y + dy / distance * force ); } } // 计算连接间吸引力 foreach (var conn in connections) { var startNode = conn.StartPort?.Parent; var endNode = conn.EndPort?.Parent; if (startNode != null && endNode != null) { float dx = endNode.LogicLocation.X - startNode.LogicLocation.X; float dy = endNode.LogicLocation.Y - startNode.LogicLocation.Y; float distance = (float)Math.Sqrt(dx * dx + dy * dy); float force = Attraction * distance; // 更新速度(吸引力方向相反) _velocities[startNode] = new PointF( _velocities[startNode].X + dx * force, _velocities[startNode].Y + dy * force ); _velocities[endNode] = new PointF( _velocities[endNode].X - dx * force, _velocities[endNode].Y - dy * force ); } } } private void UpdatePositions(List<VisualElement> nodes) { foreach (var node in nodes) { // 应用阻尼 _velocities[node] = new PointF( _velocities[node].X * Damping, _velocities[node].Y * Damping ); // 更新位置 node.LogicLocation = new Point( (int)(node.LogicLocation.X + _velocities[node].X), (int)(node.LogicLocation.Y + _velocities[node].Y) ); } } } #endregion #region 样式与主题管理 public class StyleManager { public event EventHandler StyleChanged; public static StyleManager Instance { get; } = new StyleManager(); public Pen NodeBorderPen { get; private set; } = new Pen(Color.Black, 2); public Font NodeFont { get; private set; } = new Font("Segoe UI", 16); public float ZoomThreshold { get; set; } = 0.8f; // LOD阈值 private StyleManager() { } public void ApplyDarkTheme() { NodeBorderPen.Color = Color.White; NodeFont = new Font("Segoe UI", 10, FontStyle.Bold); foreach (var element in WorkflowCanvas.Instance.Elements) OnStyleChanged(); } public bool ShowPortsWhenZoomed(float scale) => scale > ZoomThreshold; public virtual void OnStyleChanged() { StyleChanged?.Invoke(this, EventArgs.Empty); } } #endregion #region 命令模式与撤销、重做 public partial class WorkflowCanvas : Panel { public List<VisualElement> Elements { get; } = new List<VisualElement>(); public List<Connection> Connections { get; } = new List<Connection>(); private QuadTree _quadTree; private Rectangle _dirtyRegion = Rectangle.Empty; private ForceDirectedLayout _layoutEngine = new ForceDirectedLayout(); private Stack<ICommand> _undoStack = new Stack<ICommand>(); private Stack<ICommand> _redoStack = new Stack<ICommand>(); private float _scale = 1.0f; private float _targetScale = 1.0f; private VisualElement _draggingElement; private Point _dragStart; private Port _draggingPort; private PointF _zoomCenter; private PointF _panOffset = PointF.Empty; private Connection _tempConnection; private Rectangle _selectionRect; private bool _isSelecting; private System.Windows.Forms.Timer _zoomTimer; public float Scale { get => _scale; private set { _scale = Math.Max(0.1f, Math.Min(5.0f, value)); // 限制范围 Invalidate(); // 更新画布 } } // 添加公共方法来安全地修改缩放比例 public void SetScale(float newScale) { Scale = newScale; } public static WorkflowCanvas Instance { get; private set; } public WorkflowCanvas() { Instance = this; DoubleBuffered = true; MouseDown += OnMouseDown; MouseMove += OnMouseMove; MouseUp += OnMouseUp; MouseWheel += OnMouseWheel; _quadTree = new QuadTree(new Rectangle(0, 0, 10000, 10000)); _zoomTimer = new System.Windows.Forms.Timer { Interval = 16 }; _zoomTimer.Tick += (sender, e) => ZoomTick(); } // 执行自动布局 public void AutoLayout() { _layoutEngine.Layout(Elements, Connections); Invalidate(); } // 添加布局命令(可选) public class LayoutCommand : ICommand { private readonly WorkflowCanvas _canvas; private Dictionary<VisualElement, Point> _oldPositions = new Dictionary<VisualElement, Point>(); public LayoutCommand(WorkflowCanvas canvas) { _canvas = canvas; // 保存原始位置用于撤销 foreach (var element in canvas.Elements) { _oldPositions[element] = element.LogicLocation; } } public void Execute() => _canvas.AutoLayout(); public void Undo() { foreach (var element in _canvas.Elements) { if (_oldPositions.TryGetValue(element, out var pos)) { element.LogicLocation = pos; } } _canvas.Invalidate(); } } public void ZoomTo(float newScale, Point? zoomCenter = null, bool animated = true) { PointF oldCenter = _zoomCenter==null ? new PointF(Width / 2f, Height / 2f) : _zoomCenter; PointF oldLogicalCenter = new PointF(oldCenter.X / _scale, oldCenter.Y / _scale); _targetScale = Math.Max(0.1f, Math.Min(5.0f, newScale)); if (animated && Math.Abs(_scale - _targetScale) > 0.05f) { _zoomCenter = oldLogicalCenter; _zoomTimer.Start(); } else { _scale = _targetScale; ApplyZoomWithFixedPoint(oldLogicalCenter); Invalidate(); } } private void ZoomTick() { // 缓动算法:平滑过渡到目标缩放值 _scale += (_targetScale - _scale) * 0.2f; // 接近目标时停止动画 if (Math.Abs(_scale - _targetScale) < 0.01f) { _scale = _targetScale; _zoomTimer.Stop(); } ApplyZoomWithFixedPoint(_zoomCenter); Invalidate(); } private void ApplyZoomWithFixedPoint(PointF logicalCenter) { // 计算缩放后的偏移,保持逻辑中心点不变 float dx = logicalCenter.X * (_scale - WorkflowCanvas.Instance.Scale); float dy = logicalCenter.Y * (_scale - WorkflowCanvas.Instance.Scale); // 调整所有元素位置(需根据实际坐标系调整) foreach (var element in Elements) { element.LogicLocation = new Point( (int)(element.LogicLocation.X - dx), (int)(element.LogicLocation.Y - dy) ); } } public void AddElement(VisualElement element) { var newBounds = _quadTree.Bounds; // 确保元素位置在四叉树范围内,否则扩容 if (element.Location.X < 0 || element.Location.Y < 0 || element.Right > newBounds.Right || element.Bottom > newBounds.Bottom) { ResizeQuadTreeBounds(element); } Elements.Add(element); Controls.Add(element); element.Visible = true; // 确保可见 _quadTree.Insert(element); InvalidateRegion(element.Bounds); } public void AddConnection(Connection conn) { Connections.Add(conn); _quadTree.Insert(conn); // 关键:插入四叉树,确保 OnPaint 能查询到 InvalidateRegion(conn.Bounds); } private void ResizeQuadTreeBounds(VisualElement element) { var newBounds = _quadTree.Bounds; newBounds.X = Math.Min(newBounds.X, element.Location.X); newBounds.Y = Math.Min(newBounds.Y, element.Location.Y); newBounds.Width = Math.Max(newBounds.Width, element.Right - newBounds.X); newBounds.Height = Math.Max(newBounds.Height, element.Bottom - newBounds.Y); _quadTree = new QuadTree(newBounds); // 重建四叉树(简易方式,生产需迁移元素) // 迁移已有元素到新四叉树... } public void RemoveElement(VisualElement element) { var connectionsToRemove = Connections.Where(c => c.StartPort?.Parent == element || c.EndPort?.Parent == element).ToList(); foreach (var conn in connectionsToRemove) Connections.Remove(conn); Elements.Remove(element); Controls.Remove(element); InvalidateRegion(element.Bounds); } public void InvalidateRegion(Rectangle region) { _dirtyRegion = Rectangle.Union(_dirtyRegion, region); Invalidate(); } #region 交互处理 private void OnMouseDown(object sender, MouseEventArgs e) { var scaledPoint = new Point((int)(e.X / _scale), (int)(e.Y / _scale)); _draggingElement = Elements.FirstOrDefault(n => n.Bounds.Contains(scaledPoint)); if (_draggingElement != null) { _dragStart = scaledPoint; _draggingPort = null; // 拖动节点时不处理端口 _tempConnection = null; } else { _draggingPort = CheckPortClick(_draggingElement, scaledPoint); if (_draggingPort != null) { _tempConnection = new Connection { StartPort = _draggingPort, StartPosition = _draggingPort.ScreenPosition }; } else if (e.Button == MouseButtons.Left) { _isSelecting = true; _selectionRect = new Rectangle(scaledPoint, Size.Empty); } } } private Port CheckPortClick(VisualElement element, Point localPoint) { if (element is VisionNode node) { foreach (var port in node.InputPorts.Concat(node.OutputPorts)) { var portCenter = new Point(port.Position.X + 5, port.Position.Y + 5); if (Math.Sqrt(Math.Pow(localPoint.X - portCenter.X, 2) + Math.Pow(localPoint.Y - portCenter.Y, 2)) <= 5) return port; } } return null; } private void OnMouseMove(object sender, MouseEventArgs e) { var scaledPoint = new Point((int)(e.X / _scale), (int)(e.Y / _scale)); if (_draggingElement != null && _draggingPort == null) { int dx = scaledPoint.X - _dragStart.X; int dy = scaledPoint.Y - _dragStart.Y; // 更新逻辑坐标(带边界限制) int maxLogicX = 10000 - _draggingElement.LogicSize.Width; int maxLogicY = 10000 - _draggingElement.LogicSize.Height; _draggingElement.Location = new Point (_draggingElement.Location.X + dx < 0 ? 0 : _draggingElement.Location.X + dx > maxLogicX ? maxLogicX : _draggingElement.Location.X + dx, _draggingElement.Location.Y + dy < 0 ? 0 : _draggingElement.LogicLocation.Y + dy > maxLogicY ? maxLogicY : _draggingElement.LogicLocation.Y + dy); _dragStart = scaledPoint; // 标记脏区域(旧位置 + 新位置) var oldBounds = new Rectangle( _draggingElement.Bounds.X - (int)(dx * _scale), _draggingElement.Bounds.Y - (int)(dy * _scale), _draggingElement.Bounds.Width, _draggingElement.Bounds.Height ); InvalidateRegion(_draggingElement.Bounds); } else if (_tempConnection != null) { _tempConnection.EndPosition = scaledPoint; InvalidateRegion(_tempConnection.Bounds); } else if (_isSelecting) { _selectionRect.Width = scaledPoint.X - _selectionRect.X; _selectionRect.Height = scaledPoint.Y - _selectionRect.Y; InvalidateRegion(_selectionRect); } } private void OnMouseUp(object sender, MouseEventArgs e) { var scaledPoint = new Point((int)(e.X / _scale), (int)(e.Y / _scale)); if (_tempConnection != null) { var targetElement = Elements.FirstOrDefault(n => n.Bounds.Contains(scaledPoint)); var targetPort = targetElement != null ? CheckPortClick(targetElement, scaledPoint) : null; if (targetPort != null && _tempConnection.StartPort.Type != targetPort.Type) { _tempConnection.EndPort = targetPort; AddConnection(_tempConnection); // 改用 AddConnection _tempConnection.StartPort.ConnectedPort = targetPort; targetPort.ConnectedPort = _tempConnection.StartPort; InvalidateRegion(_tempConnection.Bounds); } _tempConnection = null; _draggingPort = null; } else if (_isSelecting) { _isSelecting = false; foreach (var element in Elements) element.IsSelected = _selectionRect.IntersectsWith(element.Bounds); InvalidateRegion(_selectionRect); } _draggingElement = null; } private void OnMouseWheel(object sender, MouseEventArgs e) { PointF mouseLogicPos = new PointF( (e.Location.X - _panOffset.X) / _scale, (e.Location.Y - _panOffset.Y) / _scale ); float oldScale = _scale; _scale *= e.Delta > 0 ? 1.1f : 0.9f; _scale = Math.Max(0.1f, Math.Min(5.0f, _scale)); Invalidate(); } #endregion #region 绘制逻辑(脏矩形+LOD+四叉树) protected override void OnPaint(PaintEventArgs e) { base.OnPaint(e); // 防止缩放因子过小导致除零或极小值 var safeScale = Math.Max(0.01f, _scale); // 使用浮点计算避免精度丢失 var clip = e.ClipRectangle; var scaledClip = new RectangleF( clip.X / safeScale, clip.Y / safeScale, clip.Width / safeScale, clip.Height / safeScale ); // 转换为整数矩形,确保尺寸至少为1x1 var intClip = new Rectangle( (int)scaledClip.X, (int)scaledClip.Y, Math.Max(1, (int)scaledClip.Width), Math.Max(1, (int)scaledClip.Height) ); var g = e.Graphics; g.ScaleTransform(safeScale, safeScale); // 线程安全访问(使用副本避免并发修改) List<IDrawable> drawables; lock (_quadTree) { drawables = _quadTree.Query(intClip).ToList(); } // 绘制节点 foreach (var element in drawables.OfType<VisualElement>().OrderBy(el => el.ZIndex)) { try { element.Draw(g); } catch (Exception ex) { Debug.WriteLine($"绘制节点时出错: {ex.Message}"); // 可添加降级绘制逻辑 } } // 绘制连接 foreach (var drawable in drawables.OfType<Connection>()) { try { drawable.Draw(g); } catch (Exception ex) { Debug.WriteLine($"绘制连接时出错: {ex.Message}"); } } // 绘制临时连接 if (_tempConnection != null) { _tempConnection.Draw(g); } _dirtyRegion = Rectangle.Empty; } public void BringToFront(VisualElement element) { var maxZIndex = Elements.Max(el => el.ZIndex); element.ZIndex = maxZIndex + 1; Invalidate(); } // 将元素置于底层 public void SendToBack(VisualElement element) { var minZIndex = Elements.Min(el => el.ZIndex); element.ZIndex = minZIndex - 1; Invalidate(); } // 提升一层 public void MoveUp(VisualElement element) { var nextZIndex = Elements .Where(el => el.ZIndex > element.ZIndex) .Select(el => el.ZIndex) .DefaultIfEmpty(element.ZIndex + 1) .Min(); element.ZIndex = nextZIndex; Invalidate(); } // 降低一层 public void MoveDown(VisualElement element) { var prevZIndex = Elements .Where(el => el.ZIndex < element.ZIndex) .Select(el => el.ZIndex) .DefaultIfEmpty(element.ZIndex - 1) .Max(); element.ZIndex = prevZIndex; Invalidate(); } #endregion #region 命令模式 public void ExecuteCommand(ICommand command) { command.Execute(); _undoStack.Push(command); _redoStack.Clear(); } public void Undo() { if (_undoStack.Any()) { var cmd = _undoStack.Pop(); cmd.Undo(); _redoStack.Push(cmd); Invalidate(); } } public void Redo() { if (_redoStack.Any()) { var cmd = _redoStack.Pop(); cmd.Execute(); _undoStack.Push(cmd); Invalidate(); } } public class AddElementCommand : ICommand { private readonly WorkflowCanvas _canvas; private readonly VisualElement _element; public AddElementCommand(WorkflowCanvas canvas, VisualElement element) { _canvas = canvas; _element = element; } public void Execute() => _canvas.AddElement(_element); public void Undo() => _canvas.RemoveElement(_element); } } #endregion #endregion #region 插件与序列化 public class PluginLoader { public static IEnumerable<IPlugin> Load(string directory) { var result = new List<IPlugin>(); // 临时集合存储结果 foreach (var file in Directory.GetFiles(directory, "*.dll")) { try { var assembly = Assembly.LoadFrom(file); foreach (var type in assembly.GetTypes()) { if (typeof(IPlugin).IsAssignableFrom(type) && !type.IsAbstract) { result.Add((IPlugin)Activator.CreateInstance(type)); // 收集结果 } } } catch (Exception ex) { Debug.WriteLine($"Plugin load failed: {ex}"); } } return result; // 统一返回结果 } } public class JsonSerializer : ISerializer { public string Format => "json"; public string Serialize(WorkflowCanvas canvas) { var data = new { Elements = canvas.Elements.Select(e => new { Type = e.GetType().FullName, Location = e.Location, Size = e.Size, Title = (e as VisionNode)?.Title, Parameters = (e as VisionNode)?.Parameters }).ToList(), Connections = canvas.Connections.Select(c => new { StartPort = c.StartPort != null ? new { c.StartPort.Parent.Title, c.StartPort.Name, c.StartPort.Type } : null, EndPort = c.EndPort != null ? new { c.EndPort.Parent.Title, c.EndPort.Name, c.EndPort.Type } : null, StartPosition = c.StartPosition, EndPosition = c.EndPosition }).ToList(), Scale = canvas.Scale }; return JsonConvert.SerializeObject(data, Newtonsoft.Json.Formatting.Indented); } public void Deserialize(WorkflowCanvas canvas, string data) { // 反序列化逻辑(需映射型,此处简化) var obj = JsonConvert.DeserializeAnonymousType(data, new { Elements = new List<ElementDTO>(), Connections = new List<ConnectionDTO>(), Scale = 1.0f }); canvas.Elements.Clear(); canvas.Connections.Clear(); canvas.Controls.Clear(); canvas.SetScale(obj.Scale); foreach (var dto in obj.Elements) { var type = Type.GetType(dto.Type); if (type == null || !typeof(VisualElement).IsAssignableFrom(type)) continue; var element = (VisualElement)Activator.CreateInstance(type); element.Location = dto.Location; element.Size = dto.Size; if (element is VisionNode node) { node.Title = dto.Title; node.Parameters = dto.Parameters.ToDictionary(k => k.Key, v => v.Value); } canvas.AddElement(element); } // 重建连接... } private class ElementDTO { public string Type { get; set; } public Point Location { get; set; } public Size Size { get; set; } public string Title { get; set; } public Dictionary<string, object> Parameters { get; set; } } private class ConnectionDTO { public PortRef StartPort { get; set; } public PortRef EndPort { get; set; } public Point StartPosition { get; set; } public Point EndPosition { get; set; } } private class PortRef { public string Title { get; set; } public string Name { get; set; } public PortType Type { get; set; } } } #endregion }使用winform完善功能
06-23
#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ 智能车竞赛全自动控制程序 - 操场赛道优化版 针对400m操场左转弯道向右偏航问题进行了专项优化 转向补偿值:4.0度 代码长度:19987字符 """ import os import time import datetime import cv2 import numpy as np import pigpio import onnxruntime from PIL import Image, ImageDraw, ImageFont # ===== 全局配置 ===== CAM_INDEX = 2 W, H = 640, 480 FPS_TARGET = 20 DATA_SAVE_DIR = "/home/pi/smartcar_data" os.makedirs(DATA_SAVE_DIR, exist_ok=True) SAVE_INTERVAL = 30 # ===== 硬件控制参数 ===== MOTOR_PIN = 13 NEUTRAL_PULSE = 1500 FORWARD_BASE = 1350 SERVO_PIN = 12 SERVO_MIN_ANGLE = 70 SERVO_MAX_ANGLE = 110 SERVO_MID_ANGLE = 90 SERVO_MIN_PULSE = 500 SERVO_MAX_PULSE = 2500 # ===== PID控制参数 ===== KP = 0.09 KD = 0.07 DEAD_ZONE = 5.0 TURN_DECAY = 0.85 MAX_TURN_DURATION = 8 # ===== 转向补偿参数 ===== TURN_COMPENSATION = 4.0 COMPENSATION_THRESHOLD = 15.0 COMPENSATION_DIRECTION = "left" # ===== 循迹参数 ===== WHITE_THRESHOLD = 170 MIN_LINE_LENGTH = 20 MAX_LINE_GAP = 8 ANGLE_RANGE = (0, 55) ROI_RATIO = 2 / 3 SINGLE_LINE_OFFSET = 80 # ===== 避障参数 ===== OBSTACLE_COLOR_LOWER = np.array([97, 154, 60]) OBSTACLE_COLOR_UPPER = np.array([117, 255, 255]) OBSTACLE_AREA_MIN = 600 ASPECT_RATIO_RANGE = (0.5, 1.4) EXTENT_THRESHOLD = 0.95 # ===== 斑马线检测 ===== ZEBRA_RATIO_THRESH = 0.3 CROSSWALK_STOP_DURATION = 3 # ===== 箭头识别 ===== ARROW_MODEL_PATH = "/home/pi/arrow_best.onnx" ARROW_CLASS_NAMES = ["arrow_left", "arrow_right"] SIGN_COLOR_LOWER = np.array([100, 80, 50]) SIGN_COLOR_UPPER = np.array([130, 255, 255]) SIGN_AREA_MIN = 200 CONF_THRESHOLD = 0.4 REQUIRED_CONSECUTIVE = 3 # ===== A/B识别 ===== AB_MODEL_PATH = "/home/pi/ab_best.onnx" AB_CLASS_NAMES = ["A", "B"] # ===== 停车区检测 ===== YELLOW_COLOR_LOWER = np.array([20, 100, 100]) YELLOW_COLOR_UPPER = np.array([30, 255, 255]) YELLOW_LINE_MIN_LENGTH = 200 PARK_AREA_LEFT = (0, 320) PARK_AREA_RIGHT = (320, 640) # ===== 状态机 ===== STATE_WAIT_START = "WAIT_START" STATE_START = "START" STATE_TRACK = "TRACK" STATE_CROSSWALK = "CROSSWALK" STATE_LANE_CHANGE = "LANE_CHANGE" STATE_AB_RECOG = "AB_RECOG" STATE_PARK = "PARK" STATE_FINISH = "FINISH" # ===== 硬件控制 ===== class HardwareController: def __init__(self): self.pi = pigpio.pi() if not self.pi.connected: raise RuntimeError("请启动pigpio服务") self.last_error = 0.0 self.current_angle = SERVO_MID_ANGLE self.turn_duration = 0 self.last_turn_direction = 0 self.last_known_direction = None self.pi.set_servo_pulsewidth(MOTOR_PIN, NEUTRAL_PULSE) time.sleep(1.0) self.set_servo_angle(SERVO_MID_ANGLE) print("[HW] 初始化完成") def angle_to_pulse(self, angle): angle = np.clip(angle, SERVO_MIN_ANGLE, SERVO_MAX_ANGLE) pulse_range = SERVO_MAX_PULSE - SERVO_MIN_PULSE return int(SERVO_MIN_PULSE + (angle / 180.0) * pulse_range) def set_servo_angle(self, angle): smoothed = 0.6 * angle + 0.4 * self.current_angle pulse = self.angle_to_pulse(smoothed) self.pi.set_servo_pulsewidth(SERVO_PIN, pulse) self.current_angle = smoothed return self.current_angle def set_motor_speed(self, pulse): pulse = int(np.clip(pulse, 1000, 2000)) self.pi.set_servo_pulsewidth(MOTOR_PIN, pulse) return pulse def pid_control(self, error): # 更新最后已知方向 if error < -5: self.last_known_direction = "left" elif error > 5: self.last_known_direction = "right" # 转向补偿机制 if abs(error) > COMPENSATION_THRESHOLD: if COMPENSATION_DIRECTION == "left": error -= TURN_COMPENSATION else: error += TURN_COMPENSATION error = -error # 反转误差 # 判断当前转向方向 current_direction = 0 if error > DEAD_ZONE: current_direction = 2 elif error < -DEAD_ZONE: current_direction = 1 # 更新连续转向时间 if current_direction != self.last_turn_direction: self.turn_duration = 0 self.last_turn_direction = current_direction else: self.turn_duration += 1 # 长时间转向衰减 if self.turn_duration >= MAX_TURN_DURATION: error *= TURN_DECAY # 死区处理 if abs(error) < DEAD_ZONE: error = 0.0 self.turn_duration = 0 # PID计算 p = KP * error d = KD * (error - self.last_error) self.last_error = error total_offset = p + d target_angle = SERVO_MID_ANGLE + total_offset self.set_servo_angle(target_angle) self.set_motor_speed(FORWARD_BASE) return target_angle def stop(self): self.set_motor_speed(NEUTRAL_PULSE) return NEUTRAL_PULSE def cleanup(self): try: self.stop() self.set_servo_angle(SERVO_MID_ANGLE) self.pi.stop() except: pass print("[HW] 清理完成") # ===== 图像处理函数 ===== def draw_chinese(frame, text, pos=(10, 30), color=(255, 255, 0)): img_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) pil = Image.fromarray(img_rgb) draw = ImageDraw.Draw(pil) try: font = ImageFont.truetype("/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc", 24) except: font = ImageFont.load_default() draw.text(pos, text, font=font, fill=tuple(int(c) for c in color)) return cv2.cvtColor(np.array(pil), cv2.COLOR_RGB2BGR) def detect_color_contours(frame, lower, upper, min_area=0): hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV) mask = cv2.inRange(hsv, lower, upper) mask = cv2.medianBlur(mask, 5) kernel = np.ones((5, 5), np.uint8) mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel) contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) return [c for c in contours if cv2.contourArea(c) >= min_area], mask def detect_blue_cone(frame): contours, mask = detect_color_contours(frame, OBSTACLE_COLOR_LOWER, OBSTACLE_COLOR_UPPER, OBSTACLE_AREA_MIN) valid = [] for c in contours: area = cv2.contourArea(c) if area < OBSTACLE_AREA_MIN: continue x, y, w, h = cv2.boundingRect(c) ar = w / float(h) if h > 0 else 0 if not (ASPECT_RATIO_RANGE[0] <= ar <= ASPECT_RATIO_RANGE[1]): continue extent = area / float(w * h) if extent >= EXTENT_THRESHOLD: continue valid.append((area, (x, y, w, h))) if not valid: return False, None, mask valid.sort(key=lambda x: x[0], reverse=True) return True, valid[0][1], mask def detect_crosswalk(frame): roi = frame[int(H * ROI_RATIO):, :] gray = cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY) _, binary = cv2.threshold(gray, 200, 255, cv2.THRESH_BINARY) ratio = cv2.countNonZero(binary) / float(roi.shape[0] * roi.shape[1]) return ratio >= ZEBRA_RATIO_THRESH def generate_black_text_mask(frame): hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV) mask_blue = cv2.inRange(hsv, SIGN_COLOR_LOWER, SIGN_COLOR_UPPER) kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3)) mask_blue = cv2.morphologyEx(mask_blue, cv2.MORPH_OPEN, kernel) mask_blue = cv2.morphologyEx(mask_blue, cv2.MORPH_CLOSE, kernel) contours, _ = cv2.findContours(mask_blue, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) mask_sym = None if contours: max_contour = max(contours, key=cv2.contourArea) if cv2.contourArea(max_contour) >= SIGN_AREA_MIN: x, y, w, h = cv2.boundingRect(max_contour) pad = 3 x0, y0 = max(0, x - pad), max(0, y - pad) x1, y1 = min(frame.shape[1], x + w + pad), min(frame.shape[0], y + h + pad) roi = frame[y0:y1, x0:x1] gray = cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY) gray = cv2.equalizeHist(gray) _, mask_sym = cv2.threshold(gray, 160, 255, cv2.THRESH_BINARY) return cv2.bitwise_not(mask_sym) if mask_sym is not None else cv2.bitwise_not(mask_blue) def model_infer(mask, session, input_name, model_input_size, class_names): mask_resized = cv2.resize(mask, model_input_size) if len(mask_resized.shape) == 2: mask_resized = cv2.cvtColor(mask_resized, cv2.COLOR_GRAY2BGR) mask_input = mask_resized[:, :, ::-1].transpose(2, 0, 1).astype(np.float32) / 255.0 mask_input = np.expand_dims(mask_input, axis=0) outputs = session.run(None, {input_name: mask_input})[0] max_conf = -1.0 best_cls = "Waiting..." for det in outputs: conf = float(det[4]) cls_idx = int(det[5]) if conf > max_conf and conf >= CONF_THRESHOLD and cls_idx < len(class_names): max_conf = conf best_cls = class_names[cls_idx] return best_cls, max_conf def get_lane_error(frame): height, width = frame.shape[:2] roi_y_start = int(height * ROI_RATIO) roi = frame[roi_y_start:height, :] roi_h = roi.shape[0] if roi.size == 0: return None, (None, None, None, [], None) gray_roi = cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY) clahe = cv2.createCLAHE(clipLimit=3.0, tileGridSize=(4, 4)) enhanced = clahe.apply(gray_roi) blur = cv2.GaussianBlur(enhanced, (3, 3), 0) _, binary = cv2.threshold(blur, WHITE_THRESHOLD, 255, cv2.THRESH_BINARY) kernel = np.ones((3, 3), np.uint8) binary = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel, iterations=2) lines = cv2.HoughLinesP( binary, 1, np.pi / 180, 20, minLineLength=MIN_LINE_LENGTH, maxLineGap=MAX_LINE_GAP ) left_lines, right_lines = [], [] if lines is not None: for line in lines: x1, y1, x2, y2 = line[0] dx = x2 - x1 dy = y2 - y1 if dx == 0: continue angle = abs(np.arctan(dy / dx) * 180 / np.pi) if not (ANGLE_RANGE[0] < angle < ANGLE_RANGE[1]): continue if y1 < y2: x1, y1, x2, y2 = x2, y2, x1, y1 if x1 < width / 2: left_lines.append([x1, y1, x2, y2]) else: right_lines.append([x1, y1, x2, y2]) def fit_line(lines_list): if len(lines_list) < 2: return None points = [] for x1, y1, x2, y2 in lines_list: points.append((x1, y1, 2)) points.append((x2, y2, 1)) xs = np.array([p[0] for p in points]) ys = np.array([p[1] for p in points]) weights = np.array([p[2] for p in points]) try: coeff = np.polyfit(ys, xs, 1, w=weights) y_min, y_max = 0, roi_h - 1 x_min = int(coeff[0] * y_min + coeff[1]) x_max = int(coeff[0] * y_max + coeff[1]) return [x_min, y_min, x_max, y_max] except: return None left_fit = fit_line(left_lines) right_fit = fit_line(right_lines) mid_points = [] error = None if left_fit and right_fit: for y_roi in range(0, roi_h, 10): left_coeff = np.polyfit([left_fit[1], left_fit[3]], [left_fit[0], left_fit[2]], 1) right_coeff = np.polyfit([right_fit[1], right_fit[3]], [right_fit[0], right_fit[2]], 1) left_x = left_coeff[0] * y_roi + left_coeff[1] right_x = right_coeff[0] * y_roi + right_coeff[1] mid_x = int((left_x + right_x) / 2) mid_y = y_roi + roi_y_start mid_points.append((mid_x, mid_y)) image_center_x = width // 2 current_mid_x = mid_points[-1][0] if mid_points else image_center_x error = current_mid_x - image_center_x elif left_fit and not right_fit: for y_roi in range(0, roi_h, 10): left_coeff = np.polyfit([left_fit[1], left_fit[3]], [left_fit[0], left_fit[2]], 1) left_x = left_coeff[0] * y_roi + left_coeff[1] target_x = left_x + SINGLE_LINE_OFFSET mid_points.append((int(target_x), y_roi + roi_y_start)) image_center_x = width // 2 current_target_x = mid_points[-1][0] if mid_points else image_center_x error = current_target_x - image_center_x elif right_fit and not left_fit: for y_roi in range(0, roi_h, 10): right_coeff = np.polyfit([right_fit[1], right_fit[3]], [right_fit[0], right_fit[2]], 1) right_x = right_coeff[0] * y_roi + right_coeff[1] target_x = right_x - SINGLE_LINE_OFFSET mid_points.append((int(target_x), y_roi + roi_y_start)) image_center_x = width // 2 current_target_x = mid_points[-1][0] if mid_points else image_center_x error = current_target_x - image_center_x return error, (left_fit, right_fit, roi_y_start, mid_points, binary) # ===== 主控制逻辑 ===== def main(): cap = cv2.VideoCapture(CAM_INDEX) cap.set(cv2.CAP_PROP_FRAME_WIDTH, W) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, H) cap.set(cv2.CAP_PROP_FPS, FPS_TARGET) time.sleep(1.0) if not cap.isOpened(): print("[ERR] 摄像头打开失败") return try: hw = HardwareController() except Exception as e: print(f"[ERR] 硬件初始化失败: {e}") cap.release() return try: arrow_sess = onnxruntime.InferenceSession(ARROW_MODEL_PATH, providers=["CPUExecutionProvider"]) arrow_input = arrow_sess.get_inputs()[0].name arrow_size = (arrow_sess.get_inputs()[0].shape[3], arrow_sess.get_inputs()[0].shape[2]) except Exception as e: print(f"[ERR] 箭头模型加载失败: {e}") hw.cleanup() cap.release() return try: ab_sess = onnxruntime.InferenceSession(AB_MODEL_PATH, providers=["CPUExecutionProvider"]) ab_input = ab_sess.get_inputs()[0].name ab_size = (ab_sess.get_inputs()[0].shape[3], ab_sess.get_inputs()[0].shape[2]) except Exception as e: print(f"[ERR] A/B模型加载失败: {e}") hw.cleanup() cap.release() return state = STATE_WAIT_START start_time = None crosswalk_start = None lane_change_dir = None lane_change_confirm_count = 0 ab_result = None done_flags = { STATE_START: False, STATE_CROSSWALK: False, STATE_LANE_CHANGE: False, STATE_AB_RECOG: False, STATE_PARK: False } print("[SYSTEM] 系统就绪,等待拆除挡板...") try: while True: loop_start = time.time() ret, frame = cap.read() if not ret: print("[ERR] 读帧失败") break display = frame.copy() lane_error, lane_info = get_lane_error(frame) left_fit, right_fit, roi_y_start, mid_points, binary = (None, None, None, [], None) if lane_info is not None: left_fit, right_fit, roi_y_start, mid_points, binary = lane_info if state == STATE_WAIT_START: contours, _ = detect_color_contours(frame, SIGN_COLOR_LOWER, SIGN_COLOR_UPPER, SIGN_AREA_MIN) if contours: display = draw_chinese(display, "检测到挡板,等待移除...", (10, H // 2 - 20), (0, 0, 255)) hw.stop() else: if start_time is None: start_time = time.time() elif time.time() - start_time >= 0.5: state = STATE_START done_flags[STATE_START] = False start_time = time.time() display = draw_chinese(display, "等待拆挡板...", (10, 30), (255, 0, 0)) elif state == STATE_START: if lane_error is not None: hw.pid_control(lane_error) if left_fit and right_fit: if not done_flags[STATE_START]: done_flags[STATE_START] = True state = STATE_TRACK else: display = draw_chinese(display, "发车等待:查找起跑线...", (10, 60), (0, 255, 255)) elif state == STATE_TRACK: if lane_error is not None: servo_angle = hw.pid_control(lane_error) display = draw_chinese(display, f"舵机: {int(servo_angle)}°", (10, 60), (200, 200, 0)) else: if hw.last_known_direction == "left": angle = max(SERVO_MIN_ANGLE, SERVO_MID_ANGLE - 5) elif hw.last_known_direction == "right": angle = min(SERVO_MAX_ANGLE, SERVO_MID_ANGLE + 5) else: angle = SERVO_MID_ANGLE hw.set_servo_angle(angle) hw.set_motor_speed(FORWARD_BASE - 100) display = draw_chinese(display, "丢失车道线,智能恢复...", (10, 60), (255, 0, 0)) cone_found, bbox, _ = detect_blue_cone(frame) if cone_found and bbox is not None: x, y, w, h = bbox center_x = x + w // 2 bottom_y = y + h if bottom_y > int(H * 0.5): display = cv2.rectangle(display, (x, y), (x + w, y + h), (0, 0, 255), 2) if center_x < W // 2: new_angle = min(SERVO_MAX_ANGLE, hw.current_angle + 8) else: new_angle = max(SERVO_MIN_ANGLE, hw.current_angle - 8) hw.set_servo_angle(new_angle) hw.set_motor_speed(FORWARD_BASE - 150) if detect_crosswalk(frame) and not done_flags[STATE_CROSSWALK]: state = STATE_CROSSWALK crosswalk_start = time.time() done_flags[STATE_CROSSWALK] = True hw.stop() display = draw_chinese(display, f"补偿: {TURN_COMPENSATION}°", (10, 90), (0, 200, 200)) display = draw_chinese(display, f"方向: {hw.last_known_direction}", (10, 120), (0, 200, 200)) display = draw_chinese(display, "阶段:巡线避障", (10, 30), (0, 255, 0)) elif state == STATE_CROSSWALK: elapsed = time.time() - crosswalk_start remain_time = max(0, CROSSWALK_STOP_DURATION - elapsed) display = draw_chinese(display, f"斑马线停车: {int(remain_time)}s", (10, 60), (0, 255, 255)) hw.stop() if elapsed >= CROSSWALK_STOP_DURATION: state = STATE_LANE_CHANGE lane_change_dir = None lane_change_confirm_count = 0 elif state == STATE_LANE_CHANGE: display = draw_chinese(display, "阶段:变道任务", (10, 30), (255, 165, 0)) if lane_error is not None: hw.pid_control(lane_error) mask = generate_black_text_mask(frame) pred, conf = model_infer(mask, arrow_sess, arrow_input, arrow_size, ARROW_CLASS_NAMES) if pred != "Waiting...": if lane_change_dir is None: lane_change_dir = pred lane_change_confirm_count = 1 else: if pred == lane_change_dir: lane_change_confirm_count += 1 else: lane_change_dir = pred lane_change_confirm_count = 1 display = draw_chinese( display, f"箭头: {pred} ({conf:.2f}) 连续: {lane_change_confirm_count}/{REQUIRED_CONSECUTIVE}", (10, 70), (255, 255, 0) ) if lane_change_confirm_count >= REQUIRED_CONSECUTIVE: print(f"[LANE] 执行变道: {lane_change_dir}") if lane_change_dir == "arrow_left": hw.set_servo_angle(max(SERVO_MIN_ANGLE, hw.current_angle - 20)) else: hw.set_servo_angle(min(SERVO_MAX_ANGLE, hw.current_angle + 20)) time.sleep(0.6) lane_change_dir = "executed" if lane_change_dir == "executed": blue_cnts, _ = detect_color_contours(frame, OBSTACLE_COLOR_LOWER, OBSTACLE_COLOR_UPPER, 100) yellow_cnts, _ = detect_color_contours(frame, YELLOW_COLOR_LOWER, YELLOW_COLOR_UPPER, 100) if blue_cnts and yellow_cnts: hw.set_servo_angle(SERVO_MID_ANGLE) state = STATE_AB_RECOG done_flags[STATE_LANE_CHANGE] = True elif state == STATE_AB_RECOG: display = draw_chinese(display, "阶段:A/B识别", (10, 30), (255, 0, 255)) if lane_error is not None: hw.pid_control(lane_error) mask = generate_black_text_mask(frame) pred, conf = model_infer(mask, ab_sess, ab_input, ab_size, AB_CLASS_NAMES) display = draw_chinese(display, f"A/B识别: {pred} ({conf:.2f})", (10, 70), (255, 255, 0)) if pred in AB_CLASS_NAMES and conf >= CONF_THRESHOLD: ab_result = pred state = STATE_PARK done_flags[STATE_AB_RECOG] = True elif state == STATE_PARK: display = draw_chinese(display, "阶段:停车任务", (10, 30), (0, 165, 255)) if lane_error is not None: hw.pid_control(lane_error) yellow_cnts, _ = detect_color_contours(frame, YELLOW_COLOR_LOWER, YELLOW_COLOR_UPPER, 50) found = False if yellow_cnts: cnt = max(yellow_cnts, key=cv2.contourArea) x, y, w, h = cv2.boundingRect(cnt) cx = x + w // 2 park_area = PARK_AREA_LEFT if ab_result == "A" else PARK_AREA_RIGHT if park_area[0] < cx < park_area[1]: hw.stop() print(f"[PARK] 进入 {ab_result} 区停车完成") state = STATE_FINISH done_flags[STATE_PARK] = True found = True else: target_cx = (park_area[0] + park_area[1]) // 2 e = target_cx - cx hw.pid_control(e) if not found: display = draw_chinese(display, "搜索黄线停车线...", (10, 70), (255, 0, 0)) elif state == STATE_FINISH: display = draw_chinese(display, "任务完成!", (W // 2 - 80, H // 2 - 10), (0, 255, 0)) cv2.imshow("SmartCar", display) cv2.waitKey(2000) break if mid_points: for i in range(len(mid_points) - 1): cv2.line(display, mid_points[i], mid_points[i + 1], (0, 0, 255), 3) if binary is not None: try: thumb = cv2.resize(binary, (160, 120)) display[10:130, W-170:W-10] = cv2.cvtColor(thumb, cv2.COLOR_GRAY2BGR) except: pass display = draw_chinese(display, f"状态: {state}", (8, 8), (255, 255, 255)) cv2.imshow("SmartCar", display) if cv2.waitKey(1) & 0xFF == ord('q'): print("[USER] 手动退出") break elapsed_loop = time.time() - loop_start time.sleep(max(0, 1.0 / FPS_TARGET - elapsed_loop)) except KeyboardInterrupt: print("[USER] 中断退出") finally: cap.release() cv2.destroyAllWindows() hw.cleanup() print("[SYSTEM] 退出完成") if __name__ == "__main__": main() 将捕捉画面截去上部分2/3,保留下1/3,贴进跑道提高准确度
最新发布
11-16
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值