Pytorch Yolov11 OBB 旋轉框檢測+window部署+推理封裝 留貼記錄


Pytorch Yolov11 OBB 旋轉框檢測+window部署+推理封裝 留貼記錄


上一章寫了下【Pytorch Yolov11目標檢測+window部署+推理封裝 留貼記錄】,這一章開一下YOLOV11 OBB旋轉框檢測相關的全流程,有些和上一章重復的地方我會簡寫,要兩篇結合著看,好了,開始吧

1.數據標注和格式轉換

1.1 依舊是先下載預訓練模型/和源碼,或者程序運行時自己下載
1.2 主要講一下數據標注和數據處理成YOLO OBB需要的格式
1.2.1 標注軟件 roLabelImg
在conda虛擬環境自己部署和下載吧
和正常labelimg標注框是一樣的,多了zxcv鍵盤按鍵調整旋轉框旋轉角度的功能
文件夾規則:
images
labels_all
1.2.2 先看下標完的格式是啥樣子的
標完的格式是:name cx cy w h angle
在這里插入圖片描述
實際需要的格式是:name x1 y1 x2 y2 x3 y3 x4 y4 歸一化

0 0.289583 0.151667 0.471354 0.311667 0.436979 0.708333 0.255208 0.546667
2 0.426042 0.458333 0.457292 0.48 0.450521 0.576667 0.419271 0.555

1.2.3 先格式轉換,將xml轉為txt
分了好幾步做的,至于為什么不一步寫到位,因為我也是抄別人的,我python沒什么基礎,我只想C++能調用起來就行
1.先將標注軟件的xml轉為了四角點的xml
在這里插入圖片描述
2.然后提取出xml中的四角點和label,輸出到txt
在這里插入圖片描述
3.還不算完哈,需要先劃分訓練集和測試集,再做最后的轉換

# 文件名稱   :roxml_to_dota.py
# 功能描述   :把rolabelimg標注的xml文件轉換成dota能識別的xml文件,
#             再轉換成dota格式的txt文件
#            把旋轉框 cx,cy,w,h,angle,或者矩形框cx,cy,w,h,轉換成四點坐標x1,y1,x2,y2,x3,y3,x4,y4
import os
import xml.etree.ElementTree as ET
import mathcls_list = ['Target','label','Top']  # 修改為自己的標簽def edit_xml(xml_file, dotaxml_file):"""修改xml文件:param xml_file:xml文件的路徑:return:"""# dxml_file = open(xml_file,encoding='gbk')# tree = ET.parse(dxml_file).getroot()tree = ET.parse(xml_file)objs = tree.findall('object')for ix, obj in enumerate(objs):x0 = ET.Element("x0")  # 創建節點y0 = ET.Element("y0")x1 = ET.Element("x1")y1 = ET.Element("y1")x2 = ET.Element("x2")y2 = ET.Element("y2")x3 = ET.Element("x3")y3 = ET.Element("y3")# obj_type = obj.find('bndbox')# type = obj_type.text# print(xml_file)if (obj.find('robndbox') == None):obj_bnd = obj.find('bndbox')obj_xmin = obj_bnd.find('xmin')obj_ymin = obj_bnd.find('ymin')obj_xmax = obj_bnd.find('xmax')obj_ymax = obj_bnd.find('ymax')# 以防有負值坐標xmin = max(float(obj_xmin.text), 0)ymin = max(float(obj_ymin.text), 0)xmax = max(float(obj_xmax.text), 0)ymax = max(float(obj_ymax.text), 0)obj_bnd.remove(obj_xmin)  # 刪除節點obj_bnd.remove(obj_ymin)obj_bnd.remove(obj_xmax)obj_bnd.remove(obj_ymax)x0.text = str(xmin)y0.text = str(ymax)x1.text = str(xmax)y1.text = str(ymax)x2.text = str(xmax)y2.text = str(ymin)x3.text = str(xmin)y3.text = str(ymin)else:obj_bnd = obj.find('robndbox')obj_bnd.tag = 'bndbox'  # 修改節點名obj_cx = obj_bnd.find('cx')obj_cy = obj_bnd.find('cy')obj_w = obj_bnd.find('w')obj_h = obj_bnd.find('h')obj_angle = obj_bnd.find('angle')cx = float(obj_cx.text)cy = float(obj_cy.text)w = float(obj_w.text)h = float(obj_h.text)angle = float(obj_angle.text)obj_bnd.remove(obj_cx)  # 刪除節點obj_bnd.remove(obj_cy)obj_bnd.remove(obj_w)obj_bnd.remove(obj_h)obj_bnd.remove(obj_angle)x0.text, y0.text = rotatePoint(cx, cy, cx - w / 2, cy - h / 2, -angle)x1.text, y1.text = rotatePoint(cx, cy, cx + w / 2, cy - h / 2, -angle)x2.text, y2.text = rotatePoint(cx, cy, cx + w / 2, cy + h / 2, -angle)x3.text, y3.text = rotatePoint(cx, cy, cx - w / 2, cy + h / 2, -angle)# obj.remove(obj_type)  # 刪除節點obj_bnd.append(x0)  # 新增節點obj_bnd.append(y0)obj_bnd.append(x1)obj_bnd.append(y1)obj_bnd.append(x2)obj_bnd.append(y2)obj_bnd.append(x3)obj_bnd.append(y3)tree.write(dotaxml_file, method='xml', encoding='utf-8')  # 更新xml文件# 轉換成四點坐標
def rotatePoint(xc, yc, xp, yp, theta):xoff = xp - xcyoff = yp - yccosTheta = math.cos(theta)sinTheta = math.sin(theta)pResx = cosTheta * xoff + sinTheta * yoffpResy = - sinTheta * xoff + cosTheta * yoffreturn str(int(xc + pResx)), str(int(yc + pResy))def totxt(xml_path, out_path):# 想要生成的txt文件保存的路徑,這里可以自己修改files = os.listdir(xml_path)i = 0for file in files:tree = ET.parse(xml_path + os.sep + file)root = tree.getroot()name = file.split('.')[0]output = out_path + '/' + name + '.txt'file = open(output, 'w')i = i + 1objs = tree.findall('object')for obj in objs:cls = obj.find('name').textbox = obj.find('bndbox')x0 = int(float(box.find('x0').text))y0 = int(float(box.find('y0').text))x1 = int(float(box.find('x1').text))y1 = int(float(box.find('y1').text))x2 = int(float(box.find('x2').text))y2 = int(float(box.find('y2').text))x3 = int(float(box.find('x3').text))y3 = int(float(box.find('y3').text))if x0 < 0:x0 = 0if x1 < 0:x1 = 0if x2 < 0:x2 = 0if x3 < 0:x3 = 0if y0 < 0:y0 = 0if y1 < 0:y1 = 0if y2 < 0:y2 = 0if y3 < 0:y3 = 0for cls_index, cls_name in enumerate(cls_list):if cls == cls_name:file.write("{} {} {} {} {} {} {} {} {} {}\n".format(x0, y0, x1, y1, x2, y2, x3, y3, cls, cls_index))file.close()# print(output)print(i)if __name__ == '__main__':# -----**** 第一步:把xml文件統一轉換成旋轉框的xml文件 ****-----roxml_path = '.../ultralytics-main/datasets/data_zsyobb/labels_all'dotaxml_path = '.../ultralytics-main/datasets/data_zsyobb/dotaxml'out_path = '.../ultralytics-main/datasets/data_zsyobb/dotatxt'filelist = os.listdir(roxml_path)for file in filelist:edit_xml(os.path.join(roxml_path, file), os.path.join(dotaxml_path, file))# -----**** 第二步:把旋轉框xml文件轉換成txt格式 ****-----totxt(dotaxml_path, out_path)

1.2.4 劃分訓練集和測試集
這個代碼運行完,會在images和dotatxt文件夾下,按照比例7:3,劃分和拷貝

import os
import shutilfrom tqdm import tqdm
import randomDataset_folder = r'...\ultralytics-main\datasets\data_zsyobb'
# 把當前工作目錄改為指定路徑
os.chdir(os.path.join(Dataset_folder, 'images'))  # images : 圖片文件夾的名稱
folder = '.'  # 代表os.chdir(os.path.join(Dataset_folder, 'images'))這個路徑
imgs_list = os.listdir(folder)random.seed(123)  # 固定隨機種子,防止運行時出現bug后再次運行導致imgs_list 里面的圖片名稱順序不一致random.shuffle(imgs_list)  # 打亂val_scal = 0.3  # 驗證集比列
val_number = int(len(imgs_list) * val_scal)
val_files = imgs_list[:val_number]
train_files = imgs_list[val_number:]print('all_files:', len(imgs_list))
print('train_files:', len(train_files))
print('val_files:', len(val_files))os.mkdir('train')
for each in tqdm(train_files):shutil.move(each, 'train')os.mkdir('val')
for each in tqdm(val_files):shutil.move(each, 'val')os.chdir('../dotatxt')os.mkdir('train_original')
for each in tqdm(train_files):json_file = os.path.splitext(each)[0] + '.txt'shutil.move(json_file, 'train')os.mkdir('val_original')
for each in tqdm(val_files):json_file = os.path.splitext(each)[0] + '.txt'shutil.move(json_file, 'val')print('劃分完成')

1.2.5 最后一步了,堅持
等熟悉python后,要把這個寫到一個腳本里面,真是煩死了
1.新建labels文件夾,將dotatxt文件夾中的兩個文件夾train_original val_original拷貝過去,或者直接改了dotatxt文件夾名稱也行
2.運行該代碼,直接用pytorch的數據轉換代碼

from ultralytics.data.converter import convert_dota_to_yolo_obb
convert_dota_to_yolo_obb("...\\ultralytics-main\\datasets\\data_zsyobb")

前提: 進去convert_dota_to_yolo_obb函數,把label替換為自己的
在這里插入圖片描述
換成自己后綴的圖像
在這里插入圖片描述
1.2.6 格式轉換結束,可以開始訓練了

2.訓練模型

2.1 修改ymal文件,拷貝一個ultralytics-main\ultralytics\cfg\models\11\yolo11-obb.yaml
修改一下,加幾行就行
在這里插入圖片描述
2.2 大功告成,直接粘貼訓練源碼即可
至于調參,后面再說,先把流程走通

import argparse
from ultralytics import YOLO
def parse_opt(known=False):parser = argparse.ArgumentParser()parser.add_argument('--model', type=str, default='yolov8n-obb.pt', help='initial weights path')parser.add_argument('--epochs', type=int, default=640, help='total training epochs')parser.add_argument('--imgsz', type=int, default=640, help='train, val image size (pixels)')parser.add_argument('--batch', type=str, default=2, help='total batch size for all GPUs, -1 for autobatch')parser.add_argument('--lr0', type=str, default=0.01, help=' (float) initial learning rate (i.e. SGD=1E-2, Adam=1E-3)')parser.add_argument('--cls', type=str, default=1.5, help=' (float) cls loss gain (scale with pixels)')parser.add_argument('--data', type=str, default='..../ultralytics-main/yolo11-zsyobb.yaml', help='dataset.yaml path')parser.add_argument('--workers', type=str, default=0)parser.add_argument('--device', type=str, default=0, help='cuda device, i.e. 0 or 0,1,2,3 or cpu')return parser.parse_known_args()[0] if known else parser.parse_args()def main(opt):model = YOLO(opt.model)model.train(data=opt.data, epochs=opt.epochs, imgsz=opt.imgsz, batch=opt.batch, device=opt.device, lr0=opt.lr0, cls=opt.cls)model.val(imgsz=opt.imgsz)if __name__ == '__main__':opt = parse_opt()main(opt)

2.3 預測一下

#-------------------------------預測--------------------------
from ultralytics import YOLO
import cv2
yolo = YOLO("best_sypobb.pt", task = "detect")
result = yolo(source="..../ultralytics-main/datasets/data_zsyobb/images_all",conf=0.6,vid_stride=1,iou=0.3,save = True)
print(result[0].obb)

2.4 模型導出 torchscrip

#---------------------模型導出------------------------------
from ultralytics import YOLO
# Load a model
model = YOLO("best_sypobb.pt")
# Export the model
model.export(format="torchscript",imgsz = 640,device = 0,batch = 1)

3.windos libtorch 部署

真是見鬼了,在網上搜了好久,都沒有個直接能粘貼的源碼,都是onnx部署的,我好不容易調通了libtorch的yolo11檢測,想換個obb就不知道咋弄了,我這個部署教程寫細一點,應該是CSDN獨一份能直接粘貼的OBB libtorch部署的C++源碼
3.1 首先是環境安裝,大家直接到我上一篇的地方去看吧,首行后鏈接
3.2 直接上源碼

#include <iostream>#include <opencv2/core.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/imgcodecs.hpp>
#include <torch/torch.h>
#include <torch/script.h>using torch::indexing::Slice;
using torch::indexing::None;
struct ResizeParams
{//實際圖像的左上角點與原始圖像0 0 的偏移int top;int left;//去除灰邊的圖像寬高int imgw;int imgh;//ratio 縮放圖像和原始圖像的寬高比例float ratiow;float ratioh;
};
struct Object
{cv::RotatedRect rrect;int label;float prob;
};
float intersection_area(const Object& a, const Object& b)
{std::vector<cv::Point2f> intersection;cv::rotatedRectangleIntersection(a.rrect, b.rrect, intersection);if (intersection.empty())return 0.f;return cv::contourArea(intersection);
}
void nms_sorted_bboxes(const std::vector<Object>& objects, std::vector<int>& picked, float nms_threshold, bool agnostic = false)
{picked.clear();const int n = objects.size();std::vector<float> areas(n);for (int i = 0; i < n; i++){areas[i] = objects[i].rrect.size.area();}for (int i = 0; i < n; i++){const Object& a = objects[i];int keep = 1;for (int j = 0; j < (int)picked.size(); j++){const Object& b = objects[picked[j]];if (!agnostic && a.label != b.label)continue;// intersection over unionfloat inter_area = intersection_area(a, b);float union_area = areas[i] + areas[picked[j]] - inter_area;// float IoU = inter_area / union_area;if (inter_area / union_area > nms_threshold)keep = 0;}if (keep)picked.push_back(i);}
}
//flg true/false 分別為降序/升序
template<typename T>void vector_sort(std::vector<T>vector_input, std::vector<size_t>&idx,float flg){int num = vector_input.size();idx.resize(num);for (int i = 0; i < num; i++){idx[i] = i;}if(flg){std::sort(idx.begin(), idx.end(), [&vector_input](size_t i1, size_t i2){return vector_input[i1] > vector_input[i2]; });}else{std::sort(idx.begin(), idx.end(), [&vector_input](size_t i1, size_t i2){return vector_input[i1] < vector_input[i2]; });}
}
//開始非極大抑制前的數據準備
void PrepareData(torch::Tensor output, std::vector<Object>& objects,float conf) {objects.clear();auto output_data = output.squeeze();int num_params = output_data.size(0);  // 8int num_boxes = output_data.size(1);   // 8400int classnum = num_params - 5;std::vector<Object>Tobjvec;std::vector<float>confvec;for (int i = 0; i < num_boxes; i++) {float x_center = output_data[0][i].item<float>();  // 中心 x 坐標float y_center = output_data[1][i].item<float>();  // 中心 y 坐標float width = output_data[2][i].item<float>();     // 寬度float height = output_data[3][i].item<float>();    // 高度float angle = output_data[num_params-1][i].item<float>();    // 高度int label = 0;float maxconf = 0;for (int j = 4; j < num_params-1; j++) {float conf_ = output_data[j][i].item<float>();if (conf_ > maxconf) {maxconf = conf_;label = j - 4;}}//如果最大的置信度小于給定的閾值,跳過if (maxconf < conf) {continue;}//開始把結果賦值給結構體Object obj1;obj1.label = label;obj1.prob = maxconf;obj1.rrect = cv::RotatedRect(cv::Point2f{ x_center,y_center }, cv::Size(width, height), angle*180.0 / M_PI);Tobjvec.push_back(obj1);confvec.push_back(maxconf);}//按照概率大小進行排序,降序std::vector<size_t>sortid;vector_sort(confvec, sortid, true);for (int i = 0; i < sortid.size(); i++) {objects.push_back(Tobjvec[sortid[i]]);}
}
float generate_scale(cv::Mat& image, const std::vector<int>& target_size) {int origin_w = image.cols;int origin_h = image.rows;int target_h = target_size[0];int target_w = target_size[1];float ratio_h = static_cast<float>(target_h) / static_cast<float>(origin_h);float ratio_w = static_cast<float>(target_w) / static_cast<float>(origin_w);float resize_scale = std::min(ratio_h, ratio_w);return resize_scale;
}float letterbox(cv::Mat &input_image, cv::Mat &output_image, const std::vector<int> &target_size, ResizeParams &params) {if (input_image.cols == target_size[1] && input_image.rows == target_size[0]) {if (input_image.data == output_image.data) {return 1.;}else {output_image = input_image.clone();return 1.;}}float resize_scale = generate_scale(input_image, target_size);int new_shape_w = std::round(input_image.cols * resize_scale);int new_shape_h = std::round(input_image.rows * resize_scale);float padw = (target_size[1] - new_shape_w) / 2.;float padh = (target_size[0] - new_shape_h) / 2.;int top = std::round(padh - 0.1);int bottom = std::round(padh + 0.1);int left = std::round(padw - 0.1);int right = std::round(padw + 0.1);params.top = top;params.left = left;params.imgw = new_shape_w;params.imgh = new_shape_h;params.ratiow = resize_scale;params.ratioh = resize_scale;cv::resize(input_image, output_image,cv::Size(new_shape_w, new_shape_h),0, 0, cv::INTER_AREA);cv::copyMakeBorder(output_image, output_image, top, bottom, left, right,cv::BORDER_CONSTANT, cv::Scalar(114., 114., 114));return resize_scale;
}
void drawImage(cv::Mat &image, Object obj1, ResizeParams params)
{std::vector<cv::Scalar>colorlabel;colorlabel.push_back(cv::Scalar(255, 0, 0));colorlabel.push_back(cv::Scalar(0, 255, 0));colorlabel.push_back(cv::Scalar(0, 0, 255));cv::Point2f corners[4];obj1.rrect.points(corners);cv::Point2f ori_corners[4];for (size_t i = 0; i < 4; i++) {ori_corners[i].x = (corners[i].x - params.left) / params.ratiow;ori_corners[i].y = (corners[i].y - params.top) / params.ratioh;}for (int i = 0; i < 4; i++) {cv::line(image, ori_corners[i], ori_corners[(i + 1) % 4], colorlabel[obj1.label], 1);}
}
std::vector<float>RotatedRectToXY4(Object obj1, ResizeParams params) {cv::Point2f corners[4];obj1.rrect.points(corners);cv::Point2f ori_corners[4];for (size_t i = 0; i < 4; i++) {ori_corners[i].x = (corners[i].x - params.left) / params.ratiow;ori_corners[i].y = (corners[i].y - params.top) / params.ratioh;}std::vector<float>corner;for (size_t i = 0; i < 4; i++) {corner.push_back(ori_corners[i].x);corner.push_back(ori_corners[i].y);}corner.push_back(obj1.label);corner.push_back(obj1.prob);return corner;
}
#include<windows.h>
int main() {printf("torch::cuda::is_available:%d\n", torch::cuda::is_available());// Device//torch::Device device(torch::cuda::is_available() ? torch::kCUDA : torch::kCPU);torch::Device device(torch::kCUDA);std::vector<std::string> classes{ "pepper" };try {std::string model_path = ".../ultralytics-main/best_sypobb.torchscript";torch::jit::script::Module yolo_model;LoadLibraryA("ATen_cuda.dll");LoadLibraryA("c10_cuda.dll");LoadLibraryA("torch_cuda.dll");LoadLibraryA("torchvision.dll");yolo_model = torch::jit::load(model_path, device);yolo_model.eval();yolo_model.to(device, torch::kFloat32);ResizeParams params;// Load image and preprocesscv::Mat image = cv::imread(".../ultralytics-main/20250829-102331640.jpg");cv::Mat input_image;letterbox(image, input_image, { 640, 640 }, params);cv::cvtColor(input_image, input_image, cv::COLOR_BGR2RGB);torch::Tensor image_tensor = torch::from_blob(input_image.data, { input_image.rows, input_image.cols, 3 }, torch::kByte).to(device);image_tensor = image_tensor.toType(torch::kFloat32).div(255);image_tensor = image_tensor.permute({ 2, 0, 1 });image_tensor = image_tensor.unsqueeze(0);std::vector<torch::jit::IValue> inputs{ image_tensor };auto start = std::chrono::high_resolution_clock::now();// Inferencetorch::Tensor output = yolo_model.forward(inputs).toTensor().cpu();std::vector<Object> objects;PrepareData(output, objects, 0.6);for (int i = 0; i < objects.size(); i++) {printf("%d %d %.3f\n",i, objects[i].label, objects[i].prob);}//開始非極大抑制std::vector<int> picked;nms_sorted_bboxes(objects, picked, 0.3);for (int i = 0; i < picked.size(); i++) {printf("%d %d %d %.3f\n", i, picked[i], objects[picked[i]].label, objects[picked[i]].prob);}for (int i = 0; i < picked.size(); i++) {int id = picked[i];//繪制框drawImage(image, objects[id], params);//打印四角點std::vector<float>corner = RotatedRectToXY4(objects[id], params);for (int j = 0; j < corner.size(); j++) {printf("%.3f ", corner[j]);}printf("\n");}cv::imwrite("..../20250829-102331640_out.jpg", image);}catch (const c10::Error& e) {std::cout << e.msg() << std::endl;}return 0;
}

3.3 簡單說一下
3.3.1 就是模型加載,前向,最后得到的是1 * 8 * 8400的輸出數據
1 表示一張影像
8 表示一個框的所有參數 分別表示 cenx ceny w h conf1 conf2 conf3 angle
8400 表示有8400個框的數據
注意:如果類別更多,那就是1*(x,y,w,h,nc1,nc2,…,ncn,angle)*8400了,對應修改代碼吧
3.3.2 下來就是非極大抑制
1.首先 需要把每個框的lable確認一下,即 conf1 conf2 conf3 哪個最大就是哪一類
2.將符合置信度閾值的先拿出來
3.然后按照概率大小降序排列,非極大抑制要從最大置信度開始判斷
4.非極大抑制,主要是看IOU,這里的IOU直接利用opencv自帶的函數確定,交并比,小于nms閾值的保留下來,就是最終的預測結果
3.3.3 得到預測框后,需要把四角點坐標取出來,然后反算回原始圖像位置
1.獲取四角點位置,直接用opencv帶的函數,省去自己寫了
2.換算回去,不能直接根據寬高比反算,因為最開始縮放圖像是添加了灰條,所有要看灰條的頂和左起始像素位置和寬高比反算
3.這些代碼去上面里面扒吧
非極大抑制前,符合conf閾值的效果類似這樣
有灰條 + 一堆框框
在這里插入圖片描述

非極大抑制后,換算回原始圖像預測的效果類似這樣
代碼輸出:四角點坐標+三類(0,1,2)+置信度(0.961,0.914,0.892)
在這里插入圖片描述
換算回去的圖就不貼了,涉及項目,自己按照坐標繪制就行
貼一個其他項目應用的截圖吧,代替一下
在這里插入圖片描述

4.windos libtorch 動態庫封裝 + QT mingw調用

看上一章內容吧,一模一樣,把代碼重新組織一下就行了

5.后面有空再看下yolo pose和segment,這兩項應用暫時用不上,看后面啥時候有空吧

如果寫的對你有幫助,那就留下點贊和收藏吧…

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/95832.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/95832.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/95832.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

《Keil 開發避坑指南:STM32 頭文件加載異常與 RTE 配置問題全解決》

《Keil 開發避坑指南&#xff1a;STM32 頭文件加載異常與 RTE 配置問題全解決》文章提綱一、引言? 簡述 Keil 在 STM32 開發中的核心地位&#xff0c;指出頭文件加載和 RTE&#xff08;運行時環境&#xff09;配置是新手常遇且關鍵的問題&#xff0c;說明本文旨在為開發者提…

TortoiseGit 2.4.0.0 64位安裝教程(附詳細步驟和Git配置 附安裝包)

本教程詳細講解 ?TortoiseGit 2.4.0.0 64位版本? 的完整安裝步驟&#xff0c;包括如何運行 ?TortoiseGit-2.4.0.0-64bit.msi? 安裝包、設置安裝路徑、關聯 Git 環境&#xff0c;以及安裝后的基本配置方法&#xff0c;適合 Windows 用戶快速上手 Git 圖形化管理工具。 一、…

大數據畢業設計選題推薦-基于大數據的高級大豆農業數據分析與可視化系統-Hadoop-Spark-數據可視化-BigData

?作者主頁&#xff1a;IT畢設夢工廠? 個人簡介&#xff1a;曾從事計算機專業培訓教學&#xff0c;擅長Java、Python、PHP、.NET、Node.js、GO、微信小程序、安卓Android等項目實戰。接項目定制開發、代碼講解、答辯教學、文檔編寫、降重等。 ?文末獲取源碼? 精彩專欄推薦?…

學習機器學習能看哪些書籍

關注B站可以觀看更多實戰教學視頻&#xff1a;hallo128的個人空間 在機器學習與深度學習的知識海洋中&#xff0c;選擇合適的書籍往往是入門和進階的關鍵。以下四本經典著作各具特色&#xff0c;覆蓋了從基礎理論到實踐應用的多個維度&#xff0c;無論你是初學者還是有一定基礎…

Unity通過Object學習原型模式

原型模式簡述 依據現有的實例生成新的實例 Object的實例化方法 Object.Instantiate 克隆 original 對象并返回克隆對象 Unity中的實例&#xff1a;預制體或場景中的游戲對象 示例 方法1&#xff1a;手動創建對象并添加組件 方法2&#xff1a;使用實例化方法&#xff0c;實…

【踩坑記錄】Unity 項目中 PlasticSCM 掩蔽列表引發的 文件缺失問題排查與解決

問題描述&#xff1a; Plastic SCM 簽入時&#xff0c;彈窗提示“項xxx在該工作區中不存在” Unity 項目中 PlasticSCM 掩蔽列表引發的 文件缺失問題排查與解決 文章目錄Unity 項目中 PlasticSCM 掩蔽列表引發的 文件缺失問題排查與解決一、前言二、Unity 與 .meta 文件機制1. …

Redis實戰-附近的人實現的解決方案

1.GEO數據結構1.1實現附近的人的數據結構Redis提供的專用的數據結構來實現附近的人的操作&#xff0c;這也是企業的主流解決方案&#xff0c;建議使用這種解決方案。GEO就是Redis提供的地理坐標計算的一個數據結構&#xff0c;可以很方便的計算出來兩個地點的地理坐標&#xff…

HTML第七課:發展史

HTML第七課&#xff1a;發展史發展史快速學習平臺發展史 示例 HTML 發展史 前端三件套&#xff1a;html 、css、javascript(Js) HTML 發展史 HTML 1.0&#xff08;1993 年&#xff09; 蒂姆伯納斯 - 李&#xff08;Tim Berners - Lee&#xff09;發明了萬維網&#xff0c;同…

中國生成式引擎優化(GEO)市場分析:領先企業格局與未來趨勢分析

一、GEO市場變革中國生成式引擎優化&#xff08;Generative Engine Optimization, GEO&#xff09;市場正經歷一場深刻的變革&#xff0c;其核心在于生成式人工智能&#xff08;Generative AI&#xff09;對傳統搜索引擎和數字營銷模式的顛覆性影響。傳統搜索引擎以“提供鏈接”…

好看的背景顏色 uniapp+小程序

<view class"bg-decoration"><view class"circle-1"></view><view class"circle-2"></view><view class"circle-3"></view> </view>/* 背景裝飾 */.container{background: linear-gr…

《駕馭云原生復雜性:隱性Bug的全鏈路防御體系構建》

容器、服務網格、動態配置等抽象層為系統賦予了彈性與效率,但也像深海中的暗礁,將技術風險隱藏在標準化的接口之下。那些困擾開發者的隱性Bug,往往并非源于底層技術的缺陷,而是對抽象層運行邏輯的理解偏差、配置與業務特性的錯配,或是多組件交互時的協同失效。它們以“偶發…

vosk語音識別實戰

一、簡介 Vosk 是一個由 Alpha Cephei 團隊開發的開源離線語音識別&#xff08;ASR&#xff09;工具包。它的核心優勢在于完全離線運行和輕量級&#xff0c;使其非常適合在資源受限的環境、注重隱私的場景或需要低延遲的應用中使用。 二、核心特點 離線運行 (Offline) 這是…

鴻蒙ABC開發中的名稱混淆與反射處理策略:安全與效率的平衡

在當今的軟件開發中&#xff0c;代碼安全是一個至關重要的議題。隨著鴻蒙系統&#xff08;HarmonyOS&#xff09;的廣泛應用&#xff0c;開發者們在追求功能實現的同時&#xff0c;也必須考慮如何保護代碼不被輕易破解。名稱混淆是一種常見的代碼保護手段&#xff0c;但當反射機…

css頁面頂部底部固定,中間自適應幾種方法

以下是實現頁面頂部和底部固定、中間內容自適應的幾種常見方法&#xff0c;附代碼示例和適用場景分析&#xff1a;方法一&#xff1a;Flexbox 彈性布局 <body style"margin:0; min-height:100vh; display:flex; flex-direction:column;"><header style"…

徹底拆解 CSS?accent-color:一個屬性,省下一堆“重造輪子”的苦工

我有一支技術全面、經驗豐富的小型團隊&#xff0c;專注高效交付中等規模外包項目&#xff0c;有需要外包項目的可以聯系我既要原生控件、又要品牌配色&#xff0c;還不想偽造組件&#xff1f;能不能講透 accent-color。下面給出一版盡量“到骨頭里”的解析&#xff1b;對討厭從…

在選擇iOS代簽服務前,你必須了解的三大安全風險

選iOS代簽服務&#xff1f;這三個安全坑千萬別踩&#xff01;關于iOS代簽那些你可能忽略的安全風險。多少次因為測試設備限制、緊急分發或者企業賬號年費肉疼&#xff0c;我們不得不考慮第三方代簽服務&#xff1f;但這里頭的水&#xff0c;比想象中深。風險一&#xff1a;證書…

GitHub 熱榜項目 - 日榜(2025-09-04)

GitHub 熱榜項目 - 日榜(2025-09-04) 生成于&#xff1a;2025-09-04 統計摘要 共發現熱門項目&#xff1a;20 個 榜單類型&#xff1a;日榜 本期熱點趨勢總結 本期GitHub熱榜呈現三大技術熱點&#xff1a;AI智能體開發、架構工程化和開發者工具革新。JetBrains Koog、DeepC…

在 vue-vben-admin(v5 版本)中,使用 ECharts 圖表(豆包版)

在 vue-vben-admin&#xff08;v5版本&#xff09;中&#xff0c;使用 ECharts 圖表的方式已通過框架封裝的 vben/plugins/echarts 模塊簡化&#xff0c;結合官方示例&#xff0c;具體使用步驟如下&#xff1a; 1. 核心組件與工具導入 框架提供了封裝后的 EchartsUI 組件&#…

本地 Ai 離線視頻去水印字幕!支持字幕、動靜態水印去除!

這款功能強大的AI視頻處理工具&#xff0c;能夠有效地去除視頻中的靜態水印、動態水印以及字幕。 針對不同類型的水印和字幕&#xff0c;提供了多種去除方式&#xff0c;操作簡單&#xff0c;效果顯著。 首先【打開視頻】&#xff0c;然后在識別模式里面選擇識別模式&#xf…

1個工具管好15+網盤(批量轉存/分享實測)工具實測:批量轉存 + 自動換號 + 資源監控 賬號添加失敗 / 轉存中斷?這樣解決(含功能詳解)

電腦里裝了N個網盤客戶端&#xff1a;百度網盤存工作文件、阿里云盤放家庭照片、夸克網盤塞學習資料&#xff0c;還有迅雷、天翼云盤散落在各處——每次找文件要在5個軟件間反復切換&#xff0c;手動轉存10個文件得點幾十次鼠標&#xff0c;網盤多了反倒成了“數字負擔”。直到…