张洋 11 hours ago
parent
commit
180615a3b2

BIN
bin/Win32/Debug/zhipuzi_pos_windows/DuiLib_d.dll


BIN
bin/Win32/Debug/zhipuzi_pos_windows/ai/best.bin


BIN
bin/Win32/Debug/zhipuzi_pos_windows/ai/best.onnx


File diff suppressed because it is too large
+ 0 - 4755
bin/Win32/Debug/zhipuzi_pos_windows/ai/best.xml


BIN
bin/Win32/Debug/zhipuzi_pos_windows/ai/best_448.onnx


BIN
bin/Win32/Debug/zhipuzi_pos_windows/ai/best_800.onnx


+ 0 - 2
bin/Win32/Debug/zhipuzi_pos_windows/ai/cls.names

@@ -1,2 +0,0 @@
-car
-bus

+ 0 - 67
bin/Win32/Debug/zhipuzi_pos_windows/ai/metadata.yaml

@@ -1,67 +0,0 @@
-description: Ultralytics YOLO26n-cls model trained on E:\code\zhipuzi\yolo\dataset-old
-author: Ultralytics
-date: '2026-03-12T02:14:38.690068'
-version: 8.4.14
-license: AGPL-3.0 License (https://ultralytics.com/license)
-docs: https://docs.ultralytics.com
-stride: 1
-task: classify
-batch: 1
-imgsz:
-- 448
-- 448
-names:
-  0: baixiangguo
-  1: biandou
-  2: bingtangcheng
-  3: boluo
-  4: caomei
-  5: chelizi
-  6: fanlizhi
-  7: fanshiliu
-  8: fengshuili
-  9: haitangguo
-  10: hamigua
-  11: hongmaodan
-  12: hongti
-  13: huangguanli
-  14: huluobo
-  15: huolongguo
-  16: kuerlexiangli
-  17: kugua
-  18: lanmei
-  19: liulian
-  20: lizhi
-  21: longyan
-  22: mangguo
-  23: mihoutao
-  24: miju
-  25: mugua
-  26: nanguoli
-  27: niuyouguo
-  28: pingguo
-  29: qicheng
-  30: qiezi
-  31: qingti
-  32: renxinguo
-  33: shanzhu
-  34: shatangju
-  35: shiliu
-  36: tudou
-  37: wandou
-  38: wandoujia
-  39: xiangjiao
-  40: xigua
-  41: xueli
-  42: yali
-  43: yezi
-  44: yumi
-args:
-  batch: 1
-  fraction: 1.0
-  half: false
-  int8: false
-  dynamic: false
-  nms: false
-channels: 3
-end2end: false

BIN
bin/Win32/Debug/zhipuzi_pos_windows/ai/yolo26n-cls.onnx


BIN
bin/Win32/Debug/zhipuzi_pos_windows/ai/yolo26n.onnx


BIN
bin/Win32/Debug/zhipuzi_pos_windows/ai/yolo26s-cls.onnx


BIN
bin/Win32/Debug/zhipuzi_pos_windows/image_features.db


BIN
dll/debug/DuiLib_d.dll


BIN
res/ai/best.bin


File diff suppressed because it is too large
+ 0 - 4755
res/ai/best.xml


+ 0 - 67
res/ai/metadata.yaml

@@ -1,67 +0,0 @@
-description: Ultralytics YOLO26n-cls model trained on E:\code\zhipuzi\yolo\dataset-old
-author: Ultralytics
-date: '2026-03-12T02:14:38.690068'
-version: 8.4.14
-license: AGPL-3.0 License (https://ultralytics.com/license)
-docs: https://docs.ultralytics.com
-stride: 1
-task: classify
-batch: 1
-imgsz:
-- 448
-- 448
-names:
-  0: baixiangguo
-  1: biandou
-  2: bingtangcheng
-  3: boluo
-  4: caomei
-  5: chelizi
-  6: fanlizhi
-  7: fanshiliu
-  8: fengshuili
-  9: haitangguo
-  10: hamigua
-  11: hongmaodan
-  12: hongti
-  13: huangguanli
-  14: huluobo
-  15: huolongguo
-  16: kuerlexiangli
-  17: kugua
-  18: lanmei
-  19: liulian
-  20: lizhi
-  21: longyan
-  22: mangguo
-  23: mihoutao
-  24: miju
-  25: mugua
-  26: nanguoli
-  27: niuyouguo
-  28: pingguo
-  29: qicheng
-  30: qiezi
-  31: qingti
-  32: renxinguo
-  33: shanzhu
-  34: shatangju
-  35: shiliu
-  36: tudou
-  37: wandou
-  38: wandoujia
-  39: xiangjiao
-  40: xigua
-  41: xueli
-  42: yali
-  43: yezi
-  44: yumi
-args:
-  batch: 1
-  fraction: 1.0
-  half: false
-  int8: false
-  dynamic: false
-  nms: false
-channels: 3
-end2end: false

+ 13 - 58
zhipuzi_pos_windows/ai/SQLiteVecManager.cpp

@@ -54,9 +54,12 @@ bool SQLiteVecManager::initializeDatabase(int vectorDimension)
 
 	std::cout << "使用sqlite-vec扩展进行向量存储和搜索" << std::endl;
 
-	// 创建vec0虚拟表
+	// 创建vec0虚拟表【这里只能用TEXT,不能用CHAR,不然创建表会报错】
 	std::string sql = R"(CREATE VIRTUAL TABLE IF NOT EXISTS image_features USING vec0(
 		id INTEGER PRIMARY KEY AUTOINCREMENT,
+		food_id TEXT NOT NULL,
+		food_name TEXT UNIQUE NOT NULL,
+		image_name TEXT UNIQUE NOT NULL,
 		image_path TEXT UNIQUE NOT NULL,
 		feature_vector FLOAT[)" + std::to_string(vectorDimension) + "] distance_metric=cosine)";
 
@@ -71,9 +74,9 @@ bool SQLiteVecManager::initializeDatabase(int vectorDimension)
 	return true;
 }
 
-bool SQLiteVecManager::addFeatureVector(const std::vector<float> & features, const std::string & imagePath)
+bool SQLiteVecManager::addFeatureVector(const std::vector<float>& features, const std::string& foodId, const std::string& foodName, const std::string& imageName, const std::string& imagePath)
 {
-	const char * sql = "INSERT INTO image_features(id, image_path, feature_vector) VALUES (?, ?, ?);";
+	const char * sql = "INSERT INTO image_features(food_id, food_name, image_name, image_path, feature_vector) VALUES (?, ?, ?, ?, ?);";
 
 	sqlite3_stmt * stmt;
 	int rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
@@ -85,12 +88,12 @@ bool SQLiteVecManager::addFeatureVector(const std::vector<float> & features, con
 		return false;
 	}
 
-	sqlite3_bind_int(stmt, 1, static_cast<int>(getFeatureCount() + 1));
-	sqlite3_bind_text(stmt, 2, imagePath.c_str(), -1, SQLITE_STATIC);
-
-	//std::string blobData = vectorToBlob(features);
-	sqlite3_bind_blob(stmt, 3, features.data(), features.size() * sizeof(float), SQLITE_STATIC);
-
+	//sqlite3_bind_int(stmt, 1, static_cast<int>(getFeatureCount() + 1));
+	sqlite3_bind_text(stmt, 1, foodId.c_str(), -1, SQLITE_STATIC);
+	sqlite3_bind_text(stmt, 2, foodName.c_str(), -1, SQLITE_STATIC);
+	sqlite3_bind_text(stmt, 3, imageName.c_str(), -1, SQLITE_STATIC);
+	sqlite3_bind_text(stmt, 4, imagePath.c_str(), -1, SQLITE_STATIC);
+	sqlite3_bind_blob(stmt, 5, features.data(), features.size() * sizeof(float), SQLITE_STATIC);
 	rc = sqlite3_step(stmt);
 
 	if (rc != SQLITE_DONE) {
@@ -110,11 +113,9 @@ std::vector<std::pair<std::string, float>> SQLiteVecManager::searchSimilarVector
 	std::vector<std::pair<std::string, float>> results;
 
 	std::cout << "使用sqlite-vec扩展进行向量搜索" << std::endl;
-	// 使用sqlite-vec的向量搜索功能
-	std::string blobData = vectorToBlob(queryVector);
 
 	std::string sql = 
-		"SELECT image_path, distance "
+		"SELECT food_id, food_name, image_name, image_path, distance "
 		"FROM image_features "
 		"WHERE feature_vector MATCH ?1 "
 		"ORDER BY distance "
@@ -191,52 +192,6 @@ bool SQLiteVecManager::isEmpty() const
 	return getFeatureCount() == 0;
 }
 
-std::string SQLiteVecManager::vectorToBlob(const std::vector<float> & vec)
-{
-	std::string vecStr;
-
-	for (std::size_t i = 0; i < vec.size(); i++) {
-		vecStr += std::to_string(vec[i]);
-		if (i != vec.size() - 1) {
-			vecStr += ",";
-		}
-	}
-	return vecStr;
-}
-
-std::vector<float> SQLiteVecManager::blobToVector(const std::string & blob)
-{
-	const float * data = reinterpret_cast<const float *>(blob.data());
-	size_t count = blob.size() / sizeof(float);
-	return std::vector<float>(data, data + count);
-}
-
-float SQLiteVecManager::calculateCosineSimilarity(const std::vector<float> & vec1, const std::vector<float> & vec2)
-{
-	if (vec1.size() != vec2.size() || vec1.empty())
-	{
-		return 0.0f;
-	}
-
-	float dotProduct = 0.0f;
-	float norm1 = 0.0f;
-	float norm2 = 0.0f;
-
-	for (size_t i = 0; i < vec1.size(); ++i)
-	{
-		dotProduct += vec1[i] * vec2[i];
-		norm1 += vec1[i] * vec1[i];
-		norm2 += vec2[i] * vec2[i];
-	}
-
-	if (norm1 == 0.0f || norm2 == 0.0f)
-	{
-		return 0.0f;
-	}
-
-	return dotProduct / (std::sqrt(norm1) * std::sqrt(norm2));
-}
-
 float SQLiteVecManager::distanceToSimilarity(float distance)
 {
 	return 1.0f - distance;

+ 1 - 4
zhipuzi_pos_windows/ai/SQLiteVecManager.h

@@ -16,7 +16,7 @@ public:
 	~SQLiteVecManager();
 
 	bool initializeDatabase(int vectorDimension);
-	bool addFeatureVector(const std::vector<float> & features, const std::string & imagePath);
+	bool addFeatureVector(const std::vector<float> & features, const std::string& foodId, const std::string& foodName, const std::string& imageName, const std::string & imagePath);
 	std::vector<std::pair<std::string, float>> searchSimilarVectors(const std::vector<float> & queryVector, int k = 5);
 	void saveDatabase();
 	bool loadDatabase();
@@ -24,8 +24,5 @@ public:
 	bool isEmpty() const;
 
 private:
-	std::string vectorToBlob(const std::vector<float> & vec);
-	std::vector<float> blobToVector(const std::string & blob);
-	float calculateCosineSimilarity(const std::vector<float> & vec1, const std::vector<float> & vec2);
 	float distanceToSimilarity(float distance);
 };

+ 3 - 1
zhipuzi_pos_windows/ai/test.cpp

@@ -13,6 +13,7 @@
 
 #include "../tool/debuglog.h"
 
+/*
 int AITest()
 {
 	try
@@ -160,4 +161,5 @@ int AITest()
 	}
 
 	return 0;
-}
+}
+*/

+ 47 - 0
zhipuzi_pos_windows/helper/CLewaimaiString.cpp

@@ -666,4 +666,51 @@ bool CLewaimaiString::is_only_number(std::string str)
 	}
 
 	return true;
+}
+
+std::string CLewaimaiString::generateRandomStr(size_t len, bool useTimeSeed)
+{
+	// 1. 定义字符集:大小写字母 + 数字(可自行增删)
+	const std::string charset = "0123456789"
+		"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+		"abcdefghijklmnopqrstuvwxyz";
+	if (len == 0 || charset.empty())
+	{
+		return "";
+	}
+
+	// 2. 生成时间种子(关键:精确到微秒,不同时间种子一定不同)
+	std::random_device rd;  // 硬件随机数(辅助增强随机性)
+	uint64_t timeSeed = 0;
+
+	if (useTimeSeed)
+	{
+		// 获取当前系统时间戳(从1970年1月1日到现在的微秒数)
+		auto now = std::chrono::system_clock::now().time_since_epoch();
+		timeSeed = std::chrono::duration_cast<std::chrono::microseconds>(now).count();
+	}
+
+	// 3. 初始化随机数生成器(混合硬件随机数 + 时间种子)
+	std::mt19937_64 rng;
+	if (useTimeSeed)
+	{
+		rng.seed(rd() ^ timeSeed);  // 时间+硬件双重种子
+	}
+	else
+	{
+		rng.seed(rd());
+	}
+
+	// 4. 均匀分布:随机取字符集里的字符
+	std::uniform_int_distribution<> dist(0, (int)charset.size() - 1);
+
+	// 5. 生成字符串
+	std::string result;
+	result.reserve(len);  // 预分配内存,提升效率
+	for (size_t i = 0; i < len; ++i)
+	{
+		result += charset[dist(rng)];
+	}
+
+	return result;
 }

+ 4 - 64
zhipuzi_pos_windows/helper/CLewaimaiString.h

@@ -73,67 +73,7 @@ public:
 	//判断一个字符串是否是纯数字组成的
 	static bool is_only_number(std::string str);
 
-	//生成随机字符串,长度为len,字符集是大小写字母和数字,生成的字符串在程序运行期间保证唯一
-	static std::string generateRandomString(int len)
-	{
-		// 线程安全的随机数生成器
-		std::lock_guard<std::mutex> lock(mtx);
-
-		// 字符集:大小写字母 + 数字
-		const std::string charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
-
-		std::string result;
-		result.reserve(len);
-
-		// 生成随机字符串
-		for (int i = 0; i < len; ++i)
-		{
-			result += charset[dis(gen) % charset.length()];
-		}
-
-		// 确保字符串唯一性
-		while (used_strings.find(result) != used_strings.end())
-		{
-			// 如果生成的字符串已存在,则重新生成
-			result.clear();
-			for (int i = 0; i < len; ++i)
-			{
-				result += charset[dis(gen) % charset.length()];
-			}
-		}
-
-		// 将新字符串加入已使用集合
-		used_strings.insert(result);
-
-		return result;
-	}
-
-	// 清除已使用的字符串集合(可选)
-	static void clearUsedStrings()
-	{
-		std::lock_guard<std::mutex> lock(mtx);
-		used_strings.clear();
-	}
-
-private:
-	static std::random_device rd;
-	static std::mt19937 gen;
-	static std::uniform_int_distribution<> dis;
-	static std::unordered_set<std::string> used_strings;
-	static std::mutex mtx;
-	static std::atomic<int> counter;
-};
-
-// 静态成员定义
-std::random_device CLewaimaiString::rd;
-std::mt19937 CLewaimaiString::gen(CLewaimaiString::rd());
-std::uniform_int_distribution<> CLewaimaiString::dis(0, 61); // 26+26+10 = 62个字符
-std::unordered_set<std::string> CLewaimaiString::used_strings;
-std::mutex CLewaimaiString::mtx;
-std::atomic<int> CLewaimaiString::counter(0);
-
-// 便捷函数
-std::string generateUniqueRandomString(int len)
-{
-	return CLewaimaiString::generateRandomString(len);
-}
+	// 生成随机字符串函数
+	// len: 字符串长度  useTimeSeed: 是否使用时间种子(默认开启)
+	static std::string generateRandomStr(size_t len, bool useTimeSeed = true);
+};

+ 19 - 6
zhipuzi_pos_windows/page/CAIxuexiPageUI.cpp

@@ -42,6 +42,12 @@ void CAIxuexiPageUI::InitShow()
 		std::thread(&CAIxuexiPageUI::HandleUpdateVideo, this).detach();
 	}	
 
+	//重新进来的时候,重置当前选中的分类和商品
+	m_cur_click_food_item = NULL;
+	CLabelUI* pLabel = static_cast<CLabelUI*>(this->FindSubControl(_T("aixuexi_page_xuanzhong_food_name")));
+	std::wstring wsShow = L"当前选中商品:无";
+	pLabel->SetText(wsShow.c_str());
+
 	//先初始化商品分类
 	this->InitFoodtypeShow();
 
@@ -543,6 +549,12 @@ void CAIxuexiPageUI::SetPos(RECT rc, bool bNeedInvalidate)
 
 void CAIxuexiPageUI::DoXuexi()
 {
+	if (m_cur_click_food_item == NULL)
+	{
+		m_pMainWnd->ShowToast(L"请先选择一个商品");
+		return;
+	}
+
 	//第一步先把当前帧保存为图片
 	std::wstring save_path = CSystem::GetTmpDir() + L"\\ai_xuexi_image";
 
@@ -551,7 +563,7 @@ void CAIxuexiPageUI::DoXuexi()
 		CSystem::CreateMultiLevel(CLewaimaiString::UnicodeToUTF8(save_path));
 	}
 
-	std::string filename = CLewaimaiString::generateRandomString(32);
+	std::string filename = CLewaimaiString::generateRandomStr(32);
 	std::wstring file_save_path = save_path + L"\\" + CLewaimaiString::UTF8ToUnicode(filename) + L".jpg";
 	std::string s_file_save_path = CLewaimaiString::UnicodeToUTF8(file_save_path);
 
@@ -572,8 +584,6 @@ void CAIxuexiPageUI::DoXuexi()
 	cv::imwrite(CLewaimaiString::UnicodeToUTF8(file_save_path), img);
 
 	//第二步把这个图片的特征向量提取出来,保存到数据库里,关联到当前选中的商品上
-	std::string sName = m_cur_click_food_item->GetFoodInfo().name;
-	std::string sFoodId = m_cur_click_food_item->GetFoodInfo().id;
 
 	//初始化数据库
 	std::wstring wsExePath = CSystem::getExePath();
@@ -581,7 +591,7 @@ void CAIxuexiPageUI::DoXuexi()
 	std::filesystem::path mainDir = wsProgramDir;
 	std::string sMainDir = mainDir.string();
 
-	std::string databasePath = sMainDir + "/image_features.db";     // SQLite数据库路径
+	std::string databasePath = sMainDir + "/db/image_features.db";     // SQLite数据库路径
 
 	// 初始化SQLite-Vec数据库
 	std::cout << "正在初始化SQLite-Vec数据库..." << std::endl;
@@ -599,7 +609,7 @@ void CAIxuexiPageUI::DoXuexi()
 	//初始化向量数据库,特征向量是1280维度
 	vecManager.initializeDatabase(1280);
 
-	std::string modelPath = sMainDir + "/ai/best_448.onnx";           // YOLO2026模型路径
+	std::string modelPath = sMainDir + "/ai/best_2026n_320.onnx";           // YOLO2026模型路径
 	YoloFeatureManager extractor;
 	extractor.loadModel(modelPath);
 
@@ -607,6 +617,9 @@ void CAIxuexiPageUI::DoXuexi()
 	std::vector<float> features = extractor.extractFeatures(s_file_save_path);
 	if (!features.empty())
 	{
-		vecManager.addFeatureVector(features, s_file_save_path);
+		std::string sName = m_cur_click_food_item->GetFoodInfo().name;
+		std::string sFoodId = m_cur_click_food_item->GetFoodInfo().id;
+
+		vecManager.addFeatureVector(features, sFoodId, sName, filename, s_file_save_path);
 	}
 }