Unity下的List,Grid,Page,Banner优化方案

2024-03-30 16:08

本文主要是介绍Unity下的List,Grid,Page,Banner优化方案,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

问题抛出

Unity的UI从NGUI到UGUI一直都在进化中,但是一直都是运行效率低。一个简洁的游戏界面会令游戏运行很舒服,但是有时候游戏就是需要大量的UI。
今天我们不谈UI的优化效率方法,网络上一搜一大把各种优化,今天我来分享下关于我们的游戏优化ScrollView的替代办法。
我们当前项目是一个轻量游戏。很大的比重都是UI,特别是各种List,Grid,Page翻页,滚动的Banner。
在早期Demo发布的时候,用的系统的ScrollView,大家知道这个显示一些数量不多的还能接受,如果数据量超大,那么就要用一些缓存逻辑来显示。而且那个拖动手感弱到不能接受。

解决办法

  • 早期方案:

再一开始使用了store商店里的SupperScrollView组件(如下图),能够很好的缓存数据,优化显示,有几种显示方案,如下图:
在这里插入图片描述
他是基于UGUI系统ScrollView,有池管理,用最少的实例化对象来显示数据,算的上很容易上手的一个组件。基本上两个函数就可以显示数据出来。
因为早期的我们游戏UI也相对简单,只有一个主列表和固定的Banner,这个方案总的来说过得去。
随着UI界面的复杂,Banner需要能横向滚动,Grid列表不但可以上下滑动还要可以左右翻动,要有Page的概念。还有比较麻烦的顶部Banner和底部Grid拖拉关系的变化,这就需要改动Drag相关底层函数,SupperScrollView已经很难去扩展了。加上之前效率测试中发现ScrollView组件本身占用CPU耗费过大,如果再多几个ScrollView,那么不可想象,所以又有了下面的解决方案。

  • 最终优化方案

经过大量翻找,要排除用系统自己的ScrollView,最好是自己重写ScrollView,终于找到了这个(OSA)Optimized ScrollView Adapter,貌似用过的人不多,看到好评不错,尝试了下感觉可以使用。
在这里插入图片描述
OSA完全抛弃了系统ScrollView,完全重写了列表,表格,并加了各种扩充,尽量表现的接近原生手感和效率,还有拖动特效等。
经过几天的测试和使用,在摸透了大部分功能后应用到项目里,发现效率更加的好,而且手感逼近原生,开心:)。
But,他并不适合Unity新手使用,他的模板类能创建简单的脚本模板,如果要加自己的东西,要改大量的代码。如果你有一颗挑战的心可以尝试下阅读和修改他的源码,进行学习和扩充。

https://docs.google.com/document/d/1OXxId2McwZ162Mm2VSd8BTvyjYjsgskT1BWkRF00wXU/edit
这个链接是我把OSA的文档的部分机翻和手动改正。内容主要是一步一步教你怎么能快速上手,后面的QA模块可能能遇到你想问的问题。这个建议还是要看一下的。或者你可以在他的网站上直接看最新的英文版手册。

学习和使用她

一些Unity初学者拿到OSA,看了帮助文档,照着做了,发现要改动下无从下手。那么本教程就是带你大致了解下他的框架,方便修改脚本。

首先看他的帮助文档,就是上文的地址。他的指引教程其实也只是创建了脚本,但是脚本的内容修改,还是要靠自己的理解。

所以这里我对他的源码进行加入一些我的理解分析,帮助大家能快速知道在什么地方改什么东西。也是写本文的另一个目的。

首先,你想要什么?
一个Grid还是一个List?区别就是Grid是表格的,支持一行多个,List只有一列。

所以在官方文档引导创建中也有选择:GridSRIA还是ListSRIA。他们创建出来的类是不同的

下面我们先从Grid来说,相比List稍微简单些。因为复杂的Grid脚本已经被他封装到基类了。

源码部分片段来自我自己的测试用例:

  1. GridAdapter
// You should modify the namespace to your own or - if you're sure there won't ever be conflicts - remove it altogether
namespace Thinbug
{// There is 1 important callback you need to implement, apart from Start(): UpdateCellViewsHolder()// See explanations belowpublic class MainGridAdapter : GridAdapter<GridParams, MainGridItemViewsHolder>{// Helper that stores data and notifies the adapter when items count changes// Can be iterated and can also have its elements accessed by the [] operatorpublic SimpleDataHelper<MainGridItemModel> Data { get; private set; }#region GridAdapter implementationpublic void InitGridAdpter(){Data = new SimpleDataHelper<MainGridItemModel>(this);// Calling this initializes internal data and prepares the adapter to handle item count changesbase.Start();}// This is called anytime a previously invisible item become visible, or after it's created, // or when anything that requires a refresh happens// Here you bind the data from the model to the item's views// *For the method's full description check the base implementationprotected override void UpdateCellViewsHolder(MainGridItemViewsHolder newOrRecycled){// In this callback, "newOrRecycled.ItemIndex" is guaranteed to always reflect the// index of item that should be represented by this views holder. You'll use this index// to retrieve the model from your data setMainGridItemModel model = Data[newOrRecycled.ItemIndex];newOrRecycled.listone.InitListOne(gameRoot.inst.HeroPlayer, XMLRoot.inst.levelXMLDict[model.idx], 1 , model.listid);LoadRoot.inst.ListOneLoad(newOrRecycled.listone.xml.fullpath, "png", newOrRecycled.listone);}// This is the best place to clear an item's views in order to prepare it from being recycled, but this is not always needed, // especially if the views' values are being overwritten anyway. Instead, this can be used to, for example, cancel an image // download request, if it's still in progress when the item goes out of the viewport.// <newItemIndex> will be non-negative if this item will be recycled as opposed to just being disabled// *For the method's full description check the base implementationprotected override void OnBeforeRecycleOrDisableCellViewsHolder(MainGridItemViewsHolder inRecycleBinOrVisible, int newItemIndex){if (inRecycleBinOrVisible.listone != null){inRecycleBinOrVisible.listone.Clear();}base.OnBeforeRecycleOrDisableCellViewsHolder(inRecycleBinOrVisible, newItemIndex);}#endregion// These are common data manipulation methods// The list containing the models is managed by you. The adapter only manages the items' sizes and the count// The adapter needs to be notified of any change that occurs in the data list. // For GridAdapters, only Refresh and ResetItems work for now#region data manipulationpublic void AddItemsAt(int index, IList<MainGridItemModel> items){//Commented: this only works with Lists. ATM, Insert for Grids only works by manually changing the list and calling NotifyListChangedExternally() after//Data.InsertItems(index, items);Data.List.InsertRange(index, items);Data.NotifyListChangedExternally();}public void RemoveItemsFrom(int index, int count){//Commented: this only works with Lists. ATM, Remove for Grids only works by manually changing the list and calling NotifyListChangedExternally() after//Data.RemoveRange(index, count);Data.List.RemoveRange(index, count);Data.NotifyListChangedExternally();}public void SetItems(IList<MainGridItemModel> items){Data.ResetItems(items);}#endregionpublic void OnDataRetrieved(MainGridItemModel[] newItems){//Commented: this only works with Lists. ATM, Insert for Grids only works by manually changing the list and calling NotifyListChangedExternally() after// Data.InsertItemsAtEnd(newItems);Data.List.Clear();Data.List.AddRange(newItems);Data.NotifyListChangedExternally();//刷新位置SetNormalizedPosition(_lastpos);}}// Class containing the data associated with an itempublic class MainGridItemModel : FrameBaseModel{public int idx;public int listid;}// This class keeps references to an item's views.// Your views holder should extend BaseItemViewsHolder for ListViews and CellViewsHolder for GridViews// The cell views holder should have a single child (usually named "Views"), which contains the actual // UI elements. A cell's root is never disabled - when a cell is removed, only its "views" GameObject will be disabledpublic class MainGridItemViewsHolder : CellViewsHolder{public SvgOne listone;//public Text titleText;// Retrieving the views from the item's root GameObjectpublic override void CollectViews(){base.CollectViews();// GetComponentAtPath is a handy extension method from frame8.Logic.Misc.Other.Extensions// which infers the variable's component from its type, so you won't need to specify it yourselfviews.GetComponentAtPath("listone", out listone);//views.GetComponentAtPath("TitleText", out titleText);}}
}
  • 头部结构说明

public class MainGridAdapter : GridAdapter<GridParams, MainGridItemViewsHolder>

MainGridAdapter ,是我的类的名字,Adapter表示他一种显示数据的方法。继承自GridAdapter(OSA内置)
GridParmams(OSA内置),这个你可以理解是对应的Prefab
MainGridItemViewsHolder ,是一个View,也就是对应的一个Grid的显示组件集合的布局、
public SimpleDataHelper Data 这个是所有数据的集合。

所以你就明白了OSA他是把Data,View,Prefab配合Adapter来处理的。列表模式也是如此。

初始化函数InitGridAdpter() ,我更喜欢在系统开始是我自己调用初始化,而不是Start函数,所以这里和模板里不一样。
调用过Init后,那么我的Adapter就准备好了,随时数据可以进入了。

这里注意下,再初始化数据的时候,组件本身一定是selfActive = true 才行。

  • 数据初始化
//初始化数据
......List<MainGridItemModel> listitem = new List<MainGridItemModel>();for (int i = 0; i < list.Length; ++i){var model = new MainGridItemModel(){idx = list[i],listid = pageid};listitem.Add(model);}_gridAdapter.OnDataRetrieved(listitem.ToArray());if (iScrollTo != -1){_gridAdapter.ScrollTo(iScrollTo);}

数据初始化调用OnDataRetrieved后,整个Adapter就开始运作起来了。

GridAdapter的核心只有UpdateCellViewsHolde,所以在这里你就可以刷新(update)数据就可以了。
注意你的组件大小,他会自动适配是几行几列,方便适配不同设备。开心吧,适配省心了。

这里不打算说Adapter里的其他函数,下面的List 里 我们再细讲。

只需要知道,只要在UpdateCellViewsHolder里刷新数据就可以了。
newOrRecycled.listone.InitListOne 就是用来初始化这个Grid数据 。
这个listone就是我的另外一个脚本,每个Grid有一个,能快速访问UGUI组件,写一些交互方法等,如下图:
在这里插入图片描述

下面来说一下稍微复杂点的List类的框架

  1. ListAdapter
// You should modify the namespace to your own or - if you're sure there won't ever be conflicts - remove it altogether
namespace Thinbug
{// There are 2 important callbacks you need to implement, apart from Start(): CreateViewsHolder() and UpdateViewsHolder()// See explanations belowpublic class MainListAdapter : OSA<MainParamsWithPrefab, FrameBaseView>{// Helper that stores data and notifies the adapter when items count changes// Can be iterated and can also have its elements accessed by the [] operatorpublic SimpleDataHelper<FrameBaseModel> Data { get; private set; }#region OSA implementationpublic void InitMainListAdapter(RectTransform freeze = null, float _freezeMoveHeight = 0f){Data = new SimpleDataHelper<FrameBaseModel>(this);// Calling this initializes internal data and prepares the adapter to handle item count changesbase.Start();}// This is called initially, as many times as needed to fill the viewport, // and anytime the viewport's size grows, thus allowing more items to be displayed// Here you create the "ViewsHolder" instance whose views will be re-used// *For the method's full description check the base implementationprotected override FrameBaseView CreateViewsHolder(int itemIndex){var modelType = Data[itemIndex].CachedType;if (modelType == typeof(FrameBannerModel)){//用来存放顶部Banner的Viewvar vh = new FrameBannerView();vh.Init(_Params.bannerFramePrefab, _Params.Content, itemIndex);if (Data[itemIndex].dataType == 1){if (BannerFreezeTrm != null){bannerFrameView = vh;   //因为只有一个bannerview所以,这里锁定可以用这个}}return vh;}if (modelType == typeof(FramePageMainModel)){//用来存放Page翻页的var vh = new FramePageMainView();vh.Init(_Params.pageFramePrefab, _Params.Content, itemIndex);return vh;}// If you want to avoid ifs, you could use a dictionary with model type as key and a Func<int, SimpleBaseHV> as value// which would point to a separate method for each model type. Then here simply return _Map[modelType](itemIndex)throw new InvalidOperationException("Unrecognized model type: " + modelType.Name);}public void OnDataRetrieved(FrameBaseModel[] newItems){Data.ResetItems(newItems);}// This is called anytime a previously invisible item become visible, or after it's created, // or when anything that requires a refresh happens// Here you bind the data from the model to the item's views// *For the method's full description check the base implementationprotected override void UpdateViewsHolder(FrameBaseView newOrRecycled){// In this callback, "newOrRecycled.ItemIndex" is guaranteed to always reflect the// index of item that should be represented by this views holder. You'll use this index// to retrieve the model from your data setFrameBaseModel model = Data[newOrRecycled.ItemIndex];newOrRecycled.UpdateViews(model);// This allows items to have different sizes by calling UpdateItemSizeOnTwinPass() for each of them after the current ComputeVisibility pass.// We're always calling it just for simplicity, but usually you'd only call it if you detect the item's size has changed (in our case// this can only happen with the Ad items, whose sizes depend on their image)ScheduleComputeVisibilityTwinPass();}protected override float UpdateItemSizeOnTwinPass(FrameBaseView viewsHolder){viewsHolder.UpdateSize();FrameBaseModel model = Data[viewsHolder.ItemIndex];viewsHolder.root.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, model.modelSize);//viewsHolder.root.SetSizeFromParentEdgeWithCurrentAnchors(_Params.Content, RectTransform.Edge.Top, model.modelSize);return viewsHolder.root.rect.height;}protected override bool IsRecyclable(FrameBaseView potentiallyRecyclable, int indexOfItemThatWillBecomeVisible, double sizeOfItemThatWillBecomeVisible){FrameBaseModel model = Data[indexOfItemThatWillBecomeVisible];return potentiallyRecyclable.CanPresentModelType(model.CachedType);}#endregion}/// <summary>/// Custom params containing a single prefab. <see cref="ItemPrefabSize"/> is calculated on first accessing and invalidated each time <see cref="InitIfNeeded(IOSA)"/> is called./// </summary>[System.Serializable]public class MainParamsWithPrefab : BaseParams{public RectTransform bannerFramePrefab, pageFramePrefab, panelPrefab, myTopFramePrefab, myPageFramePrefab;public override void InitIfNeeded(IOSA iAdapter){base.InitIfNeeded(iAdapter);//AssertValidWidthHeight(bannerFramePrefab);//AssertValidWidthHeight(pageFramePrefab);}}
}

为什么List复杂呢,我们在看下结构
public class MainListAdapter : OSA<MainParamsWithPrefab, FrameBaseView>
MainListAdapter 继承自 OSA (OSA核心类)
FrameBaseView 还是View,但是和Grid在概念上不一样了。因为列表类可以显示不同的东西。
下图是OSA Demo中的一个例子截图:
第一个行是一个进度,第二行是带头像的,有的大小高一些,这样的多样性就可以通过不同的View来实现。
在这里插入图片描述
那么就需要这里可以建立一个FrameBaseView 基类,根据数据来显示不同的View。
我的View类是这样的 :

  • FrameBaseView 基类
namespace Thinbug
{/// <summary>/// Includes common functionalities for the 3 views holders. <see cref="CanPresentModelType(Type)"/> is /// implemented in all of them to return wether the views holder can present a model of specific type (that's /// why we cache the model's type into <see cref="SimpleBaseModel.CachedType"/> inside its constructor)/// </summary>public abstract class FrameBaseView : BaseItemViewsHolder{//public Text titleText;/// <inheritdoc/>public override void CollectViews(){base.CollectViews();//root.GetComponentAtPath("TitleText", out titleText);}public abstract bool CanPresentModelType(Type modelType);/// <summary>/// Called to update the views from the specified model. /// Overriden by inheritors to update their own views after casting the model to its known type./// </summary>public virtual void UpdateViews(FrameBaseModel model){//if (titleText)//	titleText.text = "#" + ItemIndex;}/// <summary>Used to manually update the RectTransform's size based on custom rules each VH type specifies</summary>public virtual void UpdateSize(){}}
}

我的最外围的ListAdapter是分为了一个Banner,一个Page,Page里嵌套了Grid。

  • FramePageMainView

继承自FrameBaseView
我在构造函数CollectViews里初始化了Page数据。因为不会有变动,其实严格的来说应该放到UpdateViews中。

namespace Thinbug
{/// <summary>The views holder that can present an <see cref="GreenModel"/></summary>public class FramePageMainView : FrameBaseView{/// <inheritdoc/>public override void CollectViews(){base.CollectViews();MainPageListAdapter pageAdapter = root.GetComponentInChildren<MainPageListAdapter>();pageAdapter.InitPageAdpter();MainListScrollView.inst.InitPageData(pageAdapter);//root.GetComponentAtPath("ContentText", out contentText);}/// <inheritdoc/>public override bool CanPresentModelType(Type modelType){ return modelType == typeof(FramePageMainModel); }/// <inheritdoc/>public override void UpdateViews(FrameBaseModel model){base.UpdateViews(model);}}
}

下面是顶部的Banner类

  • FrameBannerView
namespace Thinbug
{/// <summary>The views holder that can present an <see cref="GreenModel"/></summary>public class FrameBannerView : FrameBaseView{public RectTransform child;public RectTransform rawHide;	//隐藏用的遮盖板public Image imgLine;	//阴影线/// <inheritdoc/>public override void CollectViews(){base.CollectViews();//如果bannle初始化了,那么显示bannle数据MainTableAdapter tableAdapter;root.GetComponentAtPath("Banner/TableParent/OSATable", out tableAdapter);tableAdapter.InitTableAdapter();MainListScrollView.inst.InitTableData(tableAdapter);MainBannerAdapter bannerAdapter;root.GetComponentAtPath("Banner/OSABanner", out bannerAdapter);bannerAdapter.InitBannerAdapter();MainListScrollView.inst.InitBannerData(bannerAdapter);root.GetComponentAtPath("Banner", out child);root.GetComponentAtPath("Banner/OSABanner", out rawHide);root.GetComponentAtPath("Banner/ImageLine", out imgLine);}/// <inheritdoc/>public override bool CanPresentModelType(Type modelType){ return modelType == typeof(FrameBannerModel); }/// <inheritdoc/>public override void UpdateViews(FrameBaseModel model){base.UpdateViews(model);//var greenModel = model as BannerFrameModel;//contentText.text = greenModel.textContent;}//头部移入隐藏public void Hide(){child.transform.SetParent(MainListScrollView.inst.bannerFreeze, false);rawHide.gameObject.SetActive(false);imgLine.gameObject.SetActive(true);}//头部移除public void Show() {child.transform.SetParent(root, false);rawHide.gameObject.SetActive(true);imgLine.gameObject.SetActive(false);}}
}

同样的在构造函数里初始化了一些要用的组件数据等。
包含在Banner里横向的TableAdpter等数据。

  • MainParamsWithPrefab

接下来看下预设Prefab类, MainParamsWithPrefab 是继承自 BaseParams,他也有了变化,对应不同的Prefab,和View对应起来。

/// <summary>/// Custom params containing a single prefab. <see cref="ItemPrefabSize"/> is calculated on first accessing and invalidated each time <see cref="InitIfNeeded(IOSA)"/> is called./// </summary>[System.Serializable]public class MainParamsWithPrefab : BaseParams{public RectTransform bannerFramePrefab, pageFramePrefab, panelPrefab, myTopFramePrefab, myPageFramePrefab;public override void InitIfNeeded(IOSA iAdapter){base.InitIfNeeded(iAdapter);//AssertValidWidthHeight(bannerFramePrefab);//AssertValidWidthHeight(pageFramePrefab);}}

最后还是数据
public SimpleDataHelper Data

所以我们看到List和Grid的区别,就是List因为可能内容不同,需要能灵活的扩充View和Prefab。

  • 数据的刷新
//初始化主框架var newModels = new FrameBaseModel[2];newModels[0] = new FrameBannerModel();newModels[0].dataType = 1;newModels[0].modelSize = mainAdapter.Parameters.bannerFramePrefab.rect.height;newModels[1] = new FramePageMainModel();newModels[1].dataType = 1;newModels[1].modelSize = mainAdapter.Viewport.rect.height - (newModels[0].modelSize - freezeMoveHeight);mainAdapter.OnDataRetrieved(newModels);

这里我们是两个数据,一个Banner,一个Page的数据。
(TableAdapter是包含在BannerAdapter的预设里的,所以是在Banner的构造函数里初始化的)

下面我们说下ListAdapter里的其他函数。

  • View创建

protected override FrameBaseView CreateViewsHolder(int itemIndex)
数据创建后,OSA会进入CreateViewsHolder函数,Grid里并不需要我们处理这个,毕竟不同的数据对应的View是不同的。
这里我根据数据类型创建不同的View就可以了。
根据不同的数据创建不同的View类。(就是上面的FrameBannerView,FramePageMainModel)

			var modelType = Data[itemIndex].CachedType;if (modelType == typeof(FrameBannerModel)){//用来存放顶部Banner的Viewvar vh = new FrameBannerView();vh.Init(_Params.bannerFramePrefab, _Params.Content, itemIndex);if (Data[itemIndex].dataType == 1){if (BannerFreezeTrm != null){bannerFrameView = vh;   //因为只有一个bannerview所以,这里锁定可以用这个}}return vh;}if (modelType == typeof(FramePageMainModel)){//用来存放Page翻页的var vh = new FramePageMainView();vh.Init(_Params.pageFramePrefab, _Params.Content, itemIndex);return vh;}。。。
  • View刷新
    protected override void UpdateViewsHolder(FrameBaseView newOrRecycled)
    在需要显示的时候,会调用到这里,刷新某个View

  • View大小
    protected override float UpdateItemSizeOnTwinPass(FrameBaseView viewsHolder)
    因为多Prefab,大小不同,所以显示时候要处理不同的大小

  • 回收
    protected override bool IsRecyclable
    因为缓存的问题,对于相同的有缓存,这里要根据类型判断是否需要回收。

总结:
经过Grid和List 两个结构的简单介绍,希望大家能迅速的熟悉OSA的一个基础框架。

就只有这样吗?
是的,简单的了解下核心代码的结构就够了,结合自己的需求去做,才能深刻的理解他。再就是大量看官方例子中的源码更加深刻的理解相应部分。

经过预设的嵌套多层OSA,可以实现比较复杂的列表显示。

自己做的一个项目里
主界面Adapter嵌套了BannerAdapter(可以左右滚动的),TitleAdapter标题栏,PageAdapter(就是下面的分页的Grid,List的不同列表体)这样的一个多嵌套结构。

Unity中运行良好,手感和效率都值得推荐。

最后,好的组件来之不易,请大家支持原版。

SuperScrollView
https://assetstore.unity.com/packages/tools/gui/ugui-super-scrollview-86572

OSA
https://assetstore.unity.com/packages/tools/gui/optimized-scrollview-adapter-68436

https://download.csdn.net/download/thinbug/12928761?spm=1001.2014.3001.5501
试用如果好用还是建议支持osa作者

这篇关于Unity下的List,Grid,Page,Banner优化方案的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/861945

相关文章

Vue3 的 shallowRef 和 shallowReactive:优化性能

大家对 Vue3 的 ref 和 reactive 都很熟悉,那么对 shallowRef 和 shallowReactive 是否了解呢? 在编程和数据结构中,“shallow”(浅层)通常指对数据结构的最外层进行操作,而不递归地处理其内部或嵌套的数据。这种处理方式关注的是数据结构的第一层属性或元素,而忽略更深层次的嵌套内容。 1. 浅层与深层的对比 1.1 浅层(Shallow) 定义

无人叉车3d激光slam多房间建图定位异常处理方案-墙体画线地图切分方案

墙体画线地图切分方案 针对问题:墙体两侧特征混淆误匹配,导致建图和定位偏差,表现为过门跳变、外月台走歪等 ·解决思路:预期的根治方案IGICP需要较长时间完成上线,先使用切分地图的工程化方案,即墙体两侧切分为不同地图,在某一侧只使用该侧地图进行定位 方案思路 切分原理:切分地图基于关键帧位置,而非点云。 理论基础:光照是直线的,一帧点云必定只能照射到墙的一侧,无法同时照到两侧实践考虑:关

HDFS—存储优化(纠删码)

纠删码原理 HDFS 默认情况下,一个文件有3个副本,这样提高了数据的可靠性,但也带来了2倍的冗余开销。 Hadoop3.x 引入了纠删码,采用计算的方式,可以节省约50%左右的存储空间。 此种方式节约了空间,但是会增加 cpu 的计算。 纠删码策略是给具体一个路径设置。所有往此路径下存储的文件,都会执行此策略。 默认只开启对 RS-6-3-1024k

使用opencv优化图片(画面变清晰)

文章目录 需求影响照片清晰度的因素 实现降噪测试代码 锐化空间锐化Unsharp Masking频率域锐化对比测试 对比度增强常用算法对比测试 需求 对图像进行优化,使其看起来更清晰,同时保持尺寸不变,通常涉及到图像处理技术如锐化、降噪、对比度增强等 影响照片清晰度的因素 影响照片清晰度的因素有很多,主要可以从以下几个方面来分析 1. 拍摄设备 相机传感器:相机传

高效+灵活,万博智云全球发布AWS无代理跨云容灾方案!

摘要 近日,万博智云推出了基于AWS的无代理跨云容灾解决方案,并与拉丁美洲,中东,亚洲的合作伙伴面向全球开展了联合发布。这一方案以AWS应用环境为基础,将HyperBDR平台的高效、灵活和成本效益优势与无代理功能相结合,为全球企业带来实现了更便捷、经济的数据保护。 一、全球联合发布 9月2日,万博智云CEO Michael Wong在线上平台发布AWS无代理跨云容灾解决方案的阐述视频,介绍了

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

Android平台播放RTSP流的几种方案探究(VLC VS ExoPlayer VS SmartPlayer)

技术背景 好多开发者需要遴选Android平台RTSP直播播放器的时候,不知道如何选的好,本文针对常用的方案,做个大概的说明: 1. 使用VLC for Android VLC Media Player(VLC多媒体播放器),最初命名为VideoLAN客户端,是VideoLAN品牌产品,是VideoLAN计划的多媒体播放器。它支持众多音频与视频解码器及文件格式,并支持DVD影音光盘,VCD影

MySQL高性能优化规范

前言:      笔者最近上班途中突然想丰富下自己的数据库优化技能。于是在查阅了多篇文章后,总结出了这篇! 数据库命令规范 所有数据库对象名称必须使用小写字母并用下划线分割 所有数据库对象名称禁止使用mysql保留关键字(如果表名中包含关键字查询时,需要将其用单引号括起来) 数据库对象的命名要能做到见名识意,并且最后不要超过32个字符 临时库表必须以tmp_为前缀并以日期为后缀,备份

SWAP作物生长模型安装教程、数据制备、敏感性分析、气候变化影响、R模型敏感性分析与贝叶斯优化、Fortran源代码分析、气候数据降尺度与变化影响分析

查看原文>>>全流程SWAP农业模型数据制备、敏感性分析及气候变化影响实践技术应用 SWAP模型是由荷兰瓦赫宁根大学开发的先进农作物模型,它综合考虑了土壤-水分-大气以及植被间的相互作用;是一种描述作物生长过程的一种机理性作物生长模型。它不但运用Richard方程,使其能够精确的模拟土壤中水分的运动,而且耦合了WOFOST作物模型使作物的生长描述更为科学。 本文让更多的科研人员和农业工作者

JavaFX应用更新检测功能(在线自动更新方案)

JavaFX开发的桌面应用属于C端,一般来说需要版本检测和自动更新功能,这里记录一下一种版本检测和自动更新的方法。 1. 整体方案 JavaFX.应用版本检测、自动更新主要涉及一下步骤: 读取本地应用版本拉取远程版本并比较两个版本如果需要升级,那么拉取更新历史弹出升级控制窗口用户选择升级时,拉取升级包解压,重启应用用户选择忽略时,本地版本标志为忽略版本用户选择取消时,隐藏升级控制窗口 2.