yolov8 tracking编码为web 和 rtsp流输出

news2024/11/19 5:36:05

1 基础工作

打开cmd
输入 conda env list
输入 conda activate py38
查看 nvidia-smi
查看 nvcc,如下图所示 cuda为11.7 ,为确认可以查看program files 下面的cuda 安装,看到11.7 就行了,读者可以自行确认自己的版本。
在这里插入图片描述
查看nvidia-smi
在这里插入图片描述
打开yolov8 track以后,发现速度超级慢, 打开cpu占用率奇高,gpu为零, 打开cmd
import torch
torch.cuda.is_available()
为false
确认pytorch安装的为cpu版本,重新安装pytorch cuda 版本,去pytorch网站,点开后复制安装命令,把cuda版本改成自己本机的版本。

在这里插入图片描述
输入命令
python yolo\v8\detect\detect_and_trk.py model=yolov8s.pt source=“d:\test.mp4” show=True
在这里插入图片描述
gpu占用率上升到40% 左右。
在这里插入图片描述

2 输出到web

接下来我们要做的工作为rtsp server 编码上传, 为了让其他机器得到结果,我们必须编程并且得到图像结果,让后使用实时传输协议直接转成rtp 流,让本机成为rtsp server,使用vlc 等工具,可以直接拉取到编码后的流。
修改代码,上传结果,先把命令修改成为
python yolo\v8\detect\detect_and_trk.py model=yolov8s.pt source=“d:\test.mp4”
然后在代码里面自己用opencv 来show,也就是增加两行代码

        cv2.imshow("qbshow",im0)
        cv2.waitKey(3)

没问题show 出来了,也就是im0 是我们在增加了选框的地方,在show 代码的基础上把图片发送出去就行,我们选择使用websocket 发送,或者socket 直接发送都行,这样我们需要一个客户端还是服务端的选择,我们选择让python成为服务端,目的是为了不让python需要断线重连,python 成为服务端有很多好处,
1 是可以直接给web传送图片
2 是可以直接给

    def write_results(self, idx, preds, batch):

        p, im, im0 = batch
        log_string = ""
        if len(im.shape) == 3:
            im = im[None]  # expand for batch dim
        self.seen += 1
        im0 = im0.copy()
        if self.webcam:  # batch_size >= 1
            log_string += f'{idx}: '
            frame = self.dataset.count
        else:
            frame = getattr(self.dataset, 'frame', 0)
        # tracker
        self.data_path = p
    
        save_path = str(self.save_dir / p.name)  # im.jpg
        self.txt_path = str(self.save_dir / 'labels' / p.stem) + ('' if self.dataset.mode == 'image' else f'_{frame}')
        log_string += '%gx%g ' % im.shape[2:]  # print string
        self.annotator = self.get_annotator(im0)
        
        det = preds[idx]
        self.all_outputs.append(det)
        if len(det) == 0:
            return log_string
        for c in det[:, 5].unique():
            n = (det[:, 5] == c).sum()  # detections per class
            log_string += f"{n} {self.model.names[int(c)]}{'s' * (n > 1)}, "
    
    
        # #..................USE TRACK FUNCTION....................
        dets_to_sort = np.empty((0,6))
        
        for x1,y1,x2,y2,conf,detclass in det.cpu().detach().numpy():
            dets_to_sort = np.vstack((dets_to_sort, 
                        np.array([x1, y1, x2, y2, conf, detclass])))
        
        tracked_dets = tracker.update(dets_to_sort)
        tracks =tracker.getTrackers()
        
        for track in tracks:
            [cv2.line(im0, (int(track.centroidarr[i][0]),
                        int(track.centroidarr[i][1])), 
                        (int(track.centroidarr[i+1][0]),
                        int(track.centroidarr[i+1][1])),
                        rand_color_list[track.id], thickness=3) 
                        for i,_ in  enumerate(track.centroidarr) 
                            if i < len(track.centroidarr)-1 ] 
        

        if len(tracked_dets)>0:
            bbox_xyxy = tracked_dets[:,:4]
            identities = tracked_dets[:, 8]
            categories = tracked_dets[:, 4]
            draw_boxes(im0, bbox_xyxy, identities, categories, self.model.names)
           
        gn = torch.tensor(im0.shape)[[1, 0, 1, 0]]  # normalization gain whwh
        cv2.imshow("qbshow",im0)
        cv2.waitKey(3)
        return log_string

2.1 web中显示

在python中加上websocket server的代码,以下是一个示例代码,读者可以自己测试,但是要应用到python 的图像处理server中,我们要做一些修改

import asyncio
import threading
import websockets
import time
CONNECTIONS = set()


async def server_recv(websocket):
    while True:
        try:
            recv_text = await websocket.recv()
            print("recv:", recv_text)
        except websockets.ConnectionClosed as e:
            # 客户端关闭连接,跳出对客户端的读取,结束函数
            print(e.code)
            await asyncio.sleep(0.01)
            break

async def server_hands(websocket):
  
    CONNECTIONS.add(websocket)
    print(CONNECTIONS)
    try:
        await websocket.wait_closed()
    finally:
        CONNECTIONS.remove(websocket) 



def message_all(message):
    websockets.broadcast(CONNECTIONS, message)   

async def handler(websocket, path):
    # 处理新的 WebSocket 连接
    print("New WebSocket route is ",path)

    try:
        await server_hands(websocket) # 握手加入队列
        await server_recv(websocket)  # 接收客户端消息并处理
     
    except websockets.exceptions.ConnectionClosedError as e:
        print(f"Connection closed unexpectedly: {e}")
    finally:
        pass
        # 处理完毕,关闭 WebSocket 连接
    print("WebSocket connection closed")


async def sockrun():
    async with websockets.serve(handler, "", 9090):
        await asyncio.Future()  # run forever

def main_thread(): 
    print("main")
    asyncio.run(sockrun())

在这里插入图片描述

2.2 修改

修改的地方为我们不能让websocket 阻塞主线程,虽然websocket为协程处理,但是依然要放到线程里面

def predict(cfg):
    init_tracker()
    random_color_list()
        
    cfg.model = cfg.model or "yolov8n.pt"
    cfg.imgsz = check_imgsz(cfg.imgsz, min_dim=2)  # check image size
    cfg.source = cfg.source if cfg.source is not None else ROOT / "assets"
    predictor = DetectionPredictor(cfg)
    predictor()


if __name__ == "__main__":
    thread = threading.Thread(target=main_thread)
    thread.start()
    predict()

值得注意的地方是:
很多人喜欢把编码做成base64 ,这样没有必要, 服务程序里面把二进制编码成base64, 消耗了cpu, 然后发送还大了很多,实际上我们直接发送二进制就行了,发送时

   cv2.waitKey(3)
   _, encimg = cv2.imencode('.jpg', im0)
   bys = np.array(encimg).tobytes()
   message_all(bys)

web端测试代码

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>websocket</title>
    
</head>
<body>
    <div>
        <button onclick="connecteClient()">开始接收</button>
    </div>

    <br>
    <div>
        <img src="" id="python_img" alt="websock" style="text-align:left; width: 640px; height: 360px;"> 
    </div>
    <script>
        function connecteClient() {
            var ws = new WebSocket("ws://127.0.0.1:9090");

            ws.onopen = function () {
                console.log("WebSocket 连接成功");
            };

            ws.onmessage = function (evt) {
                var received_msg = evt.data;
                blobToDataURI(received_msg, function (result) {
                    document.getElementById("python_img").src = result;
                })
            };

            ws.onclose = function () {
                console.log("连接关闭...");
            };
        }

       
        function blobToDataURI(blob, callback) {
            var reader = new FileReader();
            reader.readAsDataURL(blob);
            reader.onload = function (e) {
                callback(e.target.result);
            }
        }
    </script>
</body>
</html>

其中接收到二进制完成以后直接显示,不用服务端编码成base64,下图表明web端可以正常拿到jpg图片显示

在这里插入图片描述

其中message_all 是广播,只要是websocket client 我们就给他发一份,这样做,不但可以发送给web,也可以发送给rtsp server,我们让rtsp server 链接上来,取到jpg后,解码成为rgb24,然后编码成为h264,让vlc 可以链接rtsp server取到rtsp 流

3 rtsp server 编码h264 输出

我们的rtsp server 端使用c++ 编写,为了加快速度,使用了内存共享,而没有使用websocket client,当然是可以的,考虑到这里要解码,又要编码输出,还有可能需要使用硬件编码,为了提高效率,就这么做了,而且还简单,缺点就是这样多了一个问题,rtspserve只能启动在本机上。先编写一个h264Frame, h265 是一样的道理,后面再更改,暂时运行在windows上,等完成了再修改。注意以下代码只能运行在windows上

class H264Frame :public TThreadRunable
{
	const wchar_t*  sharedMemoryMutex = L"shared_memory_mutex";
	const wchar_t*  sharedMemoryName = L"shared_memory";
	LPVOID v_lpBase = NULL;
	HANDLE v_hMapFile = NULL;
	HANDLE 	m_shareMutex = NULL;
	std::shared_ptr<xop::RtspServer> v_server;
	std::string v_livename;
	std::wstring v_pathname;
	std::wstring v_pathmutex_name;

	int v_fps = 10;

	Encoder v_encoder;
	xop::MediaSessionId v_id;
	int v_init = 0;
	int v_quality = 28;
public:
	H264Frame(std::shared_ptr<xop::RtspServer> s, const char* livename, const wchar_t* pathname, xop::MediaSessionId session_id)
	{
		v_server = s;
		v_livename = livename;
		v_pathname = pathname;
		if (pathname != NULL)
		{
			v_pathmutex_name = pathname;
			v_pathmutex_name += L"_mutex";
		}
		v_id = session_id;
	}
	~H264Frame() {}

	bool Open()
	{
		if (v_pathname.empty())
			v_hMapFile = OpenFileMappingW(FILE_MAP_ALL_ACCESS, NULL, sharedMemoryName);
		else
			v_hMapFile = OpenFileMappingW(FILE_MAP_ALL_ACCESS, NULL, v_pathname.c_str());

		if (v_hMapFile == NULL)
		{
			//printf(" Waiting shared memory creation......\n");
			return false;
		}
		return true;
	}
	void Close()
	{
		if (m_shareMutex != NULL)
			ReleaseMutex(m_shareMutex);
		if (v_hMapFile != NULL)
			CloseHandle(v_hMapFile);
	}

	bool IsOpened() const
	{
		return (v_hMapFile != NULL);
	}

	void sendFrame(uint8_t* data ,int size, int sessionid)
	{
		xop::AVFrame videoFrame = { 0 };
		videoFrame.type = 0;
		videoFrame.size = size;// sizeofdata;// frame_size;
		videoFrame.timestamp = xop::H264Source::GetTimestamp();
		//videoFrame.notcopy = data;
		videoFrame.buffer.reset(new uint8_t[videoFrame.size]);
		std::memcpy(videoFrame.buffer.get(), data, videoFrame.size);
		v_server->PushFrame(sessionid, xop::channel_0, videoFrame);
	}
	int ReadFrame(int sessionid)
	{
		if (v_hMapFile)
		{

			v_lpBase = MapViewOfFile(v_hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, 0);
			unsigned char* pMemory = (unsigned char*)v_lpBase;

			int w = 0, h = 0;
			int frame_size = 0;
			std::memcpy(&w, pMemory, 4);
			std::memcpy(&h, pMemory + 4, 4);
			std::memcpy(&frame_size, pMemory + 8, 4);
			//printf("read the width %d height %d framesize %d\n", imageWidth,imageHeight, frame_size);
			uint8_t* rgb = pMemory + 12;
			if (v_init == 0)
			{
				v_encoder.Encoder_Open(v_fps, w, h, w, h, v_quality);
				v_init = 1;
			}
			
			v_encoder.RGB2YUV(rgb, w, h);
			AVPacket * pkt = v_encoder.EncodeVideo();
			//while (!*framebuf++);
			if (pkt!=NULL) {
				uint8_t* framebuf = pkt->data;
				frame_size = pkt->size;
				uint8_t* spsStart = NULL;
				int spsLen = 0;
				uint8_t* ppsStart = NULL;
				int ppsLen = 0;
				uint8_t* seStart = NULL;
				int seLen = 0;
				uint8_t* frameStart = 0;//非关键帧地址或者关键帧地址
				int frameLen = 0;
				AnalyseNalu_no0x((const uint8_t*)framebuf, frame_size, &spsStart, spsLen, &ppsStart, ppsLen, &seStart, seLen, &frameStart, frameLen);
				if (spsStart != NULL && ppsStart!=NULL)
				{
					sendFrame(spsStart, spsLen, sessionid);
					sendFrame(ppsStart, ppsLen, sessionid);
				}
				if (frameStart != NULL)
				{
					sendFrame(frameStart, frameLen, sessionid);
				}


				av_packet_free(&pkt);
			}
			UnmapViewOfFile(pMemory);
		}
		return -1;
	}

	void Run()
	{
		m_shareMutex = CreateMutexW(NULL, false, sharedMemoryMutex/*v_pathmutex_name.c_str()*/);		v_encoder.Encoder_Open(10, 4096, 1080, 4096, 1080, 27);
		while (1)
		{
			if (Open() == true)
			{
				break;
			}
			else
			{
				printf("can not open share mem error\n");
				Sleep(3000);
			}
		}
		printf("wait ok\n");
		//检查是否改变参数
		//重新开始
		int64_t m_start_clock = 0;
		float delay = 1000000.0f / (float)(v_fps);//微妙

		//合并的rgb
		uint8_t * rgb = NULL;
		uint8_t * cam = NULL;

		int64_t start_timestamp = av_gettime_relative();//  GetTimestamp32();
		while (1)
		{
			if (IsStop())
				break;
			//获取一帧
			//rgb = 
			if (v_hMapFile)
			{
				//printf("start to lock sharemutex\n");
				WaitForSingleObject(m_shareMutex, INFINITE);

				//printf("read frame\n");
				ReadFrame(v_id);

				ReleaseMutex(m_shareMutex);
			}
			else
			{
				printf("Shared memory handle error\n");
				break;
			}
			
			
			int64_t tnow = av_gettime_relative();// GetTimestamp32();
			int64_t total = tnow - start_timestamp; //總共花費的時間
			int64_t fix_consume = ++m_start_clock * delay;
			if (total < fix_consume) {
				int64_t diff = fix_consume - total;
				if (diff > 0) { //相差5000微妙以上
					std::this_thread::sleep_for(std::chrono::microseconds(diff));
				}
			}
		}
		if (v_init == 0)
		{
			v_encoder.Encoder_Close();
			v_init = 1;
		}
	}

};


int main(int argc, char **argv)
{	
	
	std::string suffix = "live";
	std::string ip = "127.0.0.1";
	std::string port = "8554";
	std::string rtsp_url = "rtsp://" + ip + ":" + port + "/" + suffix;
	
	std::shared_ptr<xop::EventLoop> event_loop(new xop::EventLoop());
	std::shared_ptr<xop::RtspServer> server = xop::RtspServer::Create(event_loop.get());

	if (!server->Start("0.0.0.0", atoi(port.c_str()))) {
		printf("RTSP Server listen on %s failed.\n", port.c_str());
		return 0;
	}

#ifdef AUTH_CONFIG
	server->SetAuthConfig("-_-", "admin", "12345");
#endif

	xop::MediaSession *session = xop::MediaSession::CreateNew("live"); 
	session->AddSource(xop::channel_0, xop::H264Source::CreateNew()); 
	//session->StartMulticast(); 
	session->AddNotifyConnectedCallback([] (xop::MediaSessionId sessionId, std::string peer_ip, uint16_t peer_port){
		printf("RTSP client connect, ip=%s, port=%hu \n", peer_ip.c_str(), peer_port);
	});
   
	session->AddNotifyDisconnectedCallback([](xop::MediaSessionId sessionId, std::string peer_ip, uint16_t peer_port) {
		printf("RTSP client disconnect, ip=%s, port=%hu \n", peer_ip.c_str(), peer_port);
	});

	xop::MediaSessionId session_id = server->AddSession(session);
      
	H264Frame h264frame(server, suffix.c_str(), L"shared_memory",session_id);

	//std::thread t1(SendFrameThread, server.get(), session_id, &h264_file);
	//t1.detach(); 

	std::cout << "Play URL: " << rtsp_url << std::endl;
	h264frame.Start();
	while (1) {
		xop::Timer::Sleep(100);
	}
	h264frame.Join();
	getchar();
	return 0;
}

完成以后,打开python 服务端,打开rtsp服务端,最后打开vlc查看
在这里插入图片描述
这样就完成了python端使用pytorch ,yolo 等等共享给c++ 的内存

其中python端的内存共享示例使用如下

import mmap
import contextlib
import time

while True:
    with contextlib.closing(mmap.mmap(-1, 25, tagname='test', access=mmap.ACCESS_READ)) as m:
        s = m.read(1024)#.replace('\x00', '')
        print(s)
    time.sleep(1)

读者可自行完成剩余的代码

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1358816.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

第 378 场 LeetCode 周赛题解

A 检查按位或是否存在尾随零 枚举&#xff1a;枚举两个元素的组合即可 class Solution { public:bool hasTrailingZeros(vector<int> &nums) {int n nums.size();for (int i 0; i < n; i)for (int j 0; j < i; j)if ((nums[i] | nums[j]) % 2 0)return tru…

无痛迁移:图解 Kubernetes 集群升级步骤

本文探究了Kubeadm集群升级工作流程&#xff0c;并以可视化方式展现。着重介绍了控制平面节点和工作节点的升级步骤&#xff0c;涵盖了kubeadm升级、节点清空、kubelet和kubectl升级&#xff0c;以及解除节点封锁的关键步骤。 这个简明扼要的指南可帮助用户理解和执行Kubernete…

视频号掀起内容新风向,这几类账号为何爆红?

12月初&#xff0c;视频号就迎来了好消息&#xff0c;官方发布消息称&#xff0c;视频号作者加入互选的门槛由10000粉调整为5000粉&#xff0c;其他条件不变。此举旨在激励更多创作者积极投入视频内容创作&#xff0c;从而获得更多商业合作的机会和收益。 为帮助大家更好地洞察…

lf 的年终总结(2023)

这一年&#xff0c; 我没有进行总结&#xff0c; 只有年终的回顾。 是的&#xff0c; 我又长了一岁&#xff0c; 同时也度过了三年的开发经历&#xff0c; 即将进入五年 Android 开发的阶段。 我只希望在新的一年里能够好好学习&#xff0c;期待有所提升。 回顾过去的生活&…

51单片机四位数码管计算器 Proteus仿真程序

目录 概要 仿真图 部分代码 资料下载地址&#xff1a;51单片机四位数码管计算器 Proteus仿真程序 概要 1.系统通过4x4的矩阵键盘输入数字及运算符。 2.可以进行4位十进制数以内的加法运算&#xff0c;如果计算结果超过4位十进制数&#xff0c;则屏幕显示E 3.可以进行加法以外…

工作中人员离岗识别摄像机

工作中人员离岗识别摄像机是一种基于人工智能技术的智能监控设备&#xff0c;能够实时识别员工离岗状态并进行记录。这种摄像机通常配备了高清摄像头、深度学习算法和数据处理系统&#xff0c;可以精准地监测员工的行为&#xff0c;提高企业的管理效率和安全性。 工作中人员离岗…

DevOps搭建(十二)-Jenkins推送镜像到Harbor详解

什么是Harbor&#xff1f;Harbor 是一个开源的企业级容器镜像仓库&#xff0c;它提供了安全、可靠、高效的镜像管理和分发功能。 Harbor 支持 Docker 镜像和 Helm Chart&#xff0c;可以与其他云原生工具和平台集成&#xff0c;如 Kubernetes、Docker Swarm 等。 使用 Harbor&a…

多线程和JVM

一&#xff0c;多线程实现的四种方式 1. 实现Runnable接口 普通实现&#xff1a; public class MyRunnable implements Runnable {Overridepublic void run() {System.out.println("线程执行中...");} }public class Main {public static void main(String[] arg…

CSS基本知识

文章目录 1. CSS 是什么2. 基本语法规范3. 引入方式3.1 内部样式表3.2 行内样式表3.3 外部样式 4. 选择器4.1 选择器的功能4.2 选择器的种类4.3 基础选择器4.3.1 标签选择器4.3.2 类选择器4.3.3 id 选择器4.3.4 通配符选择器 4.4 复合选择器4.4.1 后代选择器4.4.2 伪类选择器 5…

Flink Connector 开发

Flink Streaming Connector Flink是新一代流批统一的计算引擎&#xff0c;它需要从不同的第三方存储引擎中把数据读过来&#xff0c;进行处理&#xff0c;然后再写出到另外的存储引擎中。Connector的作用就相当于一个连接器&#xff0c;连接Flink计算引擎跟外界存储系统。Flin…

查看进程对应的路径查看端口号对应的进程ubuntu 安装ssh共享WiFi设置MyBatis 使用map类型作为参数,复杂查询(导出数据)

Linux 查询当前进程所在的路径 top 命令查询相应的进程号pid ps -ef |grep 进程名 lsof -I:端口号 netstat -anp|grep 端口号 cd /proc/进程id cwd 进程运行目录 exe 执行程序的绝对路径 cmdline 程序运行时输入的命令行命令 environ 记录了进程运行时的环境变量 fd 目录下是进…

互联网加竞赛 基于YOLO实现的口罩佩戴检测 - python opemcv 深度学习

文章目录 0 前言1 课题介绍2 算法原理2.1 算法简介2.2 网络架构 3 关键代码4 数据集4.1 安装4.2 打开4.3 选择yolo标注格式4.4 打标签4.5 保存 5 训练6 实现效果6.1 pyqt实现简单GUI6.3 视频识别效果6.4 摄像头实时识别 7 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xf…

科普:嵌入式多核并行仿真

自信息技术革命以来&#xff0c;计算机一直被应用在各种复杂的数据处理中&#xff0c;如火箭弹道&#xff0c;高能物理和生物学数据等。随着嵌入式领域的多样化需求的不断丰富&#xff0c;多核CPU的应用也越来越广泛&#xff1a;嵌入式系统通常需要同时处理多个任务和实时数据&…

数字藏品如何赋能线下实体?以 BOOMSHAKE 潮流夜店为例

此篇为报告内容精华版&#xff0c;更多详细精彩内容请点击 完整版 在数字化浪潮的推动下&#xff0c;品牌和企业正在迎来一场前所未有的变革。传统市场营销策略逐渐让位于新兴技术&#xff0c;特别是非同质化代币&#xff08;NFT&#xff09;的应用。这些技术不仅改变了品牌资…

牵绳遛狗你我他文明家园每一天,助力共建文明社区,基于YOLOv6开发构建公共场景下未牵绳遛狗检测识别系统

遛狗是每天要打卡的事情&#xff0c;狗狗生性活泼爱动&#xff0c;一天不遛就浑身难受&#xff0c;遛狗最重要的就是要拴绳了&#xff0c;牵紧文明绳是养犬人的必修课。外出遛狗时&#xff0c;主人手上的牵引绳更多是狗狗生命健康的一道重要屏障。每天的社区生活中&#xff0c;…

stable diffusion 基础教程-提示词之艺术风格用法

展现夕阳 golden hour, (rim lighting):1.2, warm tones, sun flare, soft shadows, vibrant colors, hazy glow, painterly effect, dreamy atmosphere阴影 chiaroscuro, (high contrast):1.2, dramatic shadows, bold highlights, moody atmosphere, captivating inte…

[通俗易懂]c语言中指针变量和数值之间的关系

一、指针变量的定义 在C语言中&#xff0c;指针变量是一种特殊类型的变量&#xff0c;它存储的是另一个变量的内存地址。指针变量可以用来间接访问和操作内存中的其他变量。指针变量的定义如下&#xff1a; 数据类型 *指针变量名&#xff1b;其中&#xff0c;数据类型可以是任…

年终总结——平凡又不平凡的2023

前言 总结不知道该如何写起&#xff0c;也不知该如何建立这一篇文章的大致框架&#xff0c;只知道我的2023大概也就分成两大块罢了。说起2023一整年&#xff0c;只能用平凡而又不平凡来形容&#xff0c;平凡在我依旧没有什么太突出的技术点&#xff0c;专业水平也一直处于龟速…

二刷Laravel 教程(构建页面)总结Ⅰ

L01 Laravel 教程 - Web 开发实战入门 ( Laravel 9.x ) 一、功能 1.会话控制&#xff08;登录、退出、记住我&#xff09; 2.用户功能&#xff08;注册、用户激活、密码重设、邮件发送、个人中心、用户列表、用户删除&#xff09; 3.静态页面&#xff08;首页、关于、帮助&am…

AIDEGen + Android Studio本地环境调试代码

AIDEGen是谷歌在Android10推出的一个自动生成项目配置文件的工具&#xff0c;可以 Android Studio or IntelliJ IDEA等查看调试源码. 1、下载Android Studio放在 /opt文件夹下 2、编译sdk source build/envsetup.sh lunch sdk-eng make sdk 3、查看out/host/linux-x86/fr…