count_fish_jh5_v1_bushu.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479
  1. # -*- coding:utf-8 -*-
  2. import math
  3. import os
  4. import tarfile
  5. import time
  6. import cv2
  7. import numpy as np
  8. from tqdm import tqdm
  9. from ultralytics import YOLO
  10. from hard_disk_storage import HardDiskStorage
  11. import shutil
  12. os.environ["KMP_DUPLICATE_LIB_OK"] = "TRUE"
  13. model = YOLO('20240820jh5best.pt') # load a custom model
  14. def image24to30(image):
  15. height, width, channels = image.shape
  16. new_b_range = height * 30 / 24
  17. canjian = round(new_b_range - height)
  18. new_width = width + canjian
  19. new_height = height + canjian
  20. black_image = np.zeros((new_height, new_width, 3), dtype=np.uint8)
  21. black_image[:height, :width] = image
  22. resized_image = cv2.resize(black_image, (width, height), cv2.INTER_LINEAR)
  23. return resized_image
  24. def image30to24(image):
  25. height, width, channels = image.shape
  26. new_b_range = height * 24 / 30
  27. canjian = round(height - new_b_range)
  28. image_caijian = image[:-canjian, :-canjian]
  29. resized_image = cv2.resize(image_caijian, (width, height), cv2.INTER_LINEAR)
  30. # cv2.imshow('1',resized_image)
  31. # cv2.waitKey(0)
  32. return resized_image
  33. def make_mask(mask, image):
  34. background = np.zeros(image.shape, dtype=np.uint8) + 0
  35. image[mask] = background[mask]
  36. return image
  37. def onload_mask(mask_path, IMAGE_SHAPE):
  38. mask_dict = {}
  39. all_flie_name = os.listdir(mask_path)
  40. for file_name in all_flie_name:
  41. if file_name.endswith(".bin") and file_name.startswith("mask"):
  42. key = file_name[file_name.index("_") + 1: -4]
  43. path = os.path.join(mask_path, file_name)
  44. mask = np.fromfile(path, dtype=np.bool_)
  45. mask.shape = IMAGE_SHAPE
  46. mask_dict[key] = mask
  47. return mask_dict
  48. def list_split(info_list):
  49. s_list, b_list = [], []
  50. for image_info in info_list:
  51. if "s" in image_info.name:
  52. s_list.append(image_info)
  53. elif "b" in image_info.name:
  54. b_list.append(image_info)
  55. else:
  56. print("error")
  57. return s_list, b_list
  58. def rotate(image, angle, center=None, scale=1.0):
  59. (h, w) = image.shape[:2]
  60. if center is None:
  61. center = (w // 2, h // 2)
  62. M = cv2.getRotationMatrix2D(center, angle, scale)
  63. rotated = cv2.warpAffine(image, M, (w, h))
  64. return rotated
  65. def get_contours_aear(contour):
  66. """
  67. 计算轮廓大小
  68. """
  69. x, y, w, h = cv2.boundingRect(contour)
  70. contour = contour - [x, y]
  71. p = np.zeros(shape=(h, w))
  72. # print(contour)
  73. cv2.drawContours(p, [contour], -1, 255, -1)
  74. # cv2.imshow('p',p)
  75. # cv2.waitKey(0)
  76. count_pixels = np.count_nonzero(p)
  77. # count_pixels = cv2.contourArea(contour=contour, oriented=False)
  78. return count_pixels
  79. def get_fish_count(image, image_name, fish_size=130, YUZHI=0):
  80. """
  81. 计算10m的鱼群数据
  82. """
  83. # 寻找轮廓
  84. hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
  85. H, S, V = cv2.split(hsv)
  86. # 去噪
  87. # dst_V = cv2.fastNlMeansDenoising(V, None, 10, 7, 21)
  88. kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
  89. dst_V = cv2.morphologyEx(V, cv2.MORPH_OPEN, kernel, iterations=2)
  90. ret, dst_V = cv2.threshold(dst_V, YUZHI, 255, cv2.THRESH_BINARY)
  91. contours, hierarchy = cv2.findContours(
  92. dst_V, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE
  93. )
  94. fish_count = 0
  95. for c in contours:
  96. data = np.array(get_contours_aear(c))
  97. if data > 20 and data < fish_size:
  98. fish_count = fish_count + 1
  99. elif data > fish_size:
  100. fish_count = fish_count + int(data / fish_size)
  101. return fish_count
  102. def Calculate_density(fish_count_list, r=10, angel_big=100, angel_small=20):
  103. """
  104. :param fish_count_list: 30张鱼数量列表
  105. :param r: 范围半径
  106. :param angel_big: 声呐上下角度
  107. :param angel_small: 声呐左右角度
  108. :return: 10m鱼的密度
  109. ps:
  110. 目前图像上下角度为 100° , 左右角度为 20°
  111. 体积计算过程:
  112. (1) 首先将声呐的上下范围当做180°(看作一个半圆),半圆旋转360°为球体,声呐左右角度为 20° ,此时上下角度180度,左右角度 20°的体积为球体积的 20/360。
  113. (2) 已知 上下角度180度,20, 那么上下角度100°的体积为 (上下角度180度,左右角度20度的体积) * 100/180
  114. """
  115. print("最大:", max(fish_count_list))
  116. print("最小:", min(fish_count_list))
  117. mean_fish_count = sum(fish_count_list) // len(fish_count_list)
  118. ball_volumes = math.pi * 10 * 10 * (135 / 360)
  119. density = mean_fish_count / ball_volumes
  120. print(
  121. "[ 10米 ] 鱼数量 : {} , 水面积 : {}立方米 , 单位体积鱼的数量(鱼密度): {}".format(
  122. mean_fish_count, ball_volumes, density
  123. )
  124. )
  125. return density, mean_fish_count
  126. def distance(x1, y1, x2, y2):
  127. return math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
  128. def Calculate_volume(range_shang, range_xia, rotation_angle, new_point):
  129. volume_list = []
  130. volume = 0
  131. for i in range(len(new_point)):
  132. rang = new_point[i][1]
  133. if range_shang <= rang * 24 / 630 <= range_xia:
  134. radius = (
  135. (new_point[i][0] * 24 / 630, new_point[i][0] * 24 / 630)
  136. if i == 0
  137. else ((new_point[i - 1][0] * 24 / 630, new_point[i][0] * 24 / 630))
  138. )
  139. height = (
  140. new_point[i][1] if i == 0 else new_point[i][1] - new_point[i - 1][1]
  141. )
  142. volume += (
  143. np.pi
  144. * (radius[0] ** 2 + radius[1] ** 2 + radius[0] * radius[1])
  145. * (height * 24 / 630)
  146. ) / 3
  147. if rotation_angle > 0 and range_xia == 6:
  148. jian_volume = (
  149. (
  150. np.pi
  151. * (new_point[0][0] * 24 / 630) ** 2
  152. * (
  153. math.tan(math.radians(rotation_angle))
  154. * (new_point[0][0] * 24 / 630)
  155. )
  156. )
  157. * 2
  158. / 3
  159. )
  160. else:
  161. jian_volume = 0
  162. volume_list.append(volume)
  163. volume_list.append(jian_volume)
  164. return volume_list
  165. def Detection_Clothing(A, B):
  166. y_min_A = np.min(A[:, 1])
  167. y_max_A = np.max(A[:, 1])
  168. # 在数组B中选择y值在最大值和最小值中间的坐标
  169. mask = (B[:, 1] >= y_min_A) & (B[:, 1] <= y_max_A)
  170. C = B[mask]
  171. # 判断A中任意一个坐标与C中任意一个坐标的距离是否小于100个像素
  172. distances = np.sqrt(np.sum((A[:, None] - C) ** 2, axis=-1))
  173. result_distance = np.any(distances < (0.5 * 630 / 24))
  174. # 判断A中任意一个坐标的x值是否大于C中任意一个坐标的x值
  175. result_x = np.any(A[:, 0][:, None] > C[:, 0])
  176. result = result_distance or result_x # 两个条件同时满足才返回True
  177. return result
  178. def get_fish_ratio_all(image, image_name, mask_dict, CAGE_LENGTH, fish_size=0, min_size=0):
  179. # 初始化一个列表用于存储符合条件的轮廓
  180. temp_con = []
  181. # 初始化一个计数器,用于记录符合条件的鱼的数量
  182. tmp_total_s = 0
  183. # 将输入图像从BGR颜色空间转换为HSV颜色空间
  184. hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
  185. # 将HSV图像分割成三个通道:H(色调)、S(饱和度)、V(明度)
  186. H, S, V = cv2.split(hsv)
  187. # 对V通道应用阈值操作,生成二值图像g
  188. # 阈值为60,超过阈值的像素设为255,否则设为0
  189. ret, g = cv2.threshold(V, 60, 255, cv2.THRESH_BINARY)
  190. # 在二值图像g中查找轮廓
  191. # 使用RETR_EXTERNAL模式只检测外部轮廓
  192. # 使用CHAIN_APPROX_SIMPLE模式压缩水平、垂直和对角线方向的轮廓点
  193. contours, hierarchy = cv2.findContours(
  194. g, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE
  195. )
  196. # 遍历每一个检测到的轮廓
  197. for one_contours in contours:
  198. # 计算轮廓的面积
  199. # get_contours_aear是一个函数,用于计算轮廓的面积
  200. data = np.array(get_contours_aear(one_contours))
  201. # 如果轮廓面积在min_size和fish_size之间
  202. if data > min_size and data < fish_size:
  203. # 将该轮廓添加到符合条件的轮廓列表中
  204. temp_con.append(one_contours)
  205. # 计数器加1
  206. tmp_total_s = tmp_total_s + 1
  207. # 如果轮廓面积大于fish_size
  208. elif data > fish_size:
  209. # 将该轮廓添加到符合条件的轮廓列表中
  210. temp_con.append(one_contours)
  211. # 计数器加上面积除以fish_size的值,并向上取整
  212. tmp_total_s = tmp_total_s + round(data / fish_size)
  213. # 将符合条件的鱼的计数赋值给one_image_fish_count
  214. one_image_fish_count = tmp_total_s
  215. # 计算最终的鱼数量
  216. # 这里假设每张图片代表一个20度的区域
  217. # 360度表示一个完整的环形区域,所以需要乘以18(360 / 20)来计算整个区域内的鱼数
  218. # 再乘以2可能是某种修正系数
  219. finall_num = one_image_fish_count * (360 / 20) * 2
  220. # 返回最终计算的鱼的数量,并将其转换为整数
  221. return int(finall_num)
  222. def move_gz(gz_path):
  223. target_folder = os.path.basename(gz_path)
  224. target_base_path = '/data/transfer_data'
  225. target_path = os.path.join(target_base_path, target_folder)
  226. # 检查目标文件夹是否存在,如果不存在则创建
  227. if not os.path.exists(target_path):
  228. os.makedirs(target_path)
  229. # 移动文件到目标文件夹
  230. try:
  231. shutil.move(image_tar, target_path)
  232. print(f"File moved from {image_tar} to {target_path}")
  233. except Exception as e:
  234. print(f"Error: {e}")
  235. if __name__ == "__main__":
  236. harddisk_db = HardDiskStorage()
  237. while True:
  238. flag_30to24 = 0
  239. flag_24to30 = 0
  240. # UUID = "jh5_0605_s16b38"
  241. # # cage_num = UUID.split("yue")[-1]
  242. # sql = "SELECT uuid,cage_length,fish_weight FROM jh5_measurement_data WHERE UUID = %s ORDER BY id DESC LIMIT 1;"
  243. # var = UUID
  244. # res = harddisk_db.execute_sql(sql, var)
  245. #
  246. # if len(res) == 0:
  247. # print("暂无数据")
  248. # time.sleep(30)
  249. # continue
  250. #
  251. # res = res[0]
  252. # UUID = res["uuid"]
  253. sql = "SELECT uuid,cage_length,fish_weight FROM measurement_data ORDER BY id DESC LIMIT 1;"
  254. res = harddisk_db.get_all(sql)
  255. print(res)
  256. if len(res) == 0:
  257. print("暂无数据")
  258. time.sleep(30)
  259. continue
  260. res = res[0]
  261. UUID = res["uuid"]
  262. CAGE_LENGTH = res["cage_length"]
  263. FISH_WEIGHT = res["fish_weight"]
  264. # =============================== | 1. 参数配置 | ================================
  265. fishsze = 60
  266. min_size = 10
  267. print("*" * 50)
  268. print("uuid:", UUID)
  269. print("cage_length:", CAGE_LENGTH)
  270. print("weight:", FISH_WEIGHT)
  271. print("fish_size:", fishsze)
  272. if UUID == None:
  273. print("uuid none")
  274. time.sleep(30)
  275. continue
  276. sql = "SELECT id,package_name, gz_path FROM package_data WHERE UUID = %s AND is_processed = 0 ORDER BY id ASC LIMIT 1;"
  277. var = UUID
  278. res = harddisk_db.execute_sql(sql, var)
  279. if len(res) == 0:
  280. print("暂无数据")
  281. time.sleep(30)
  282. continue
  283. res = res[0]
  284. ID = res["id"]
  285. package_name = res["package_name"]
  286. print("package_name:", package_name)
  287. print("*" * 50)
  288. gz_path = res["gz_path"]
  289. image_tar = os.path.join(gz_path, package_name)
  290. if not os.path.exists(image_tar):
  291. print('不存在gz文件')
  292. error_text = "不存在gz文件"
  293. is_abnormal = 1
  294. fish_num = None
  295. sql = "UPDATE package_data SET is_processed = 1 , fish_count = %s,is_abnormal = %s,error_text=%s WHERE id = %s; "
  296. var = (fish_num, is_abnormal, error_text, ID)
  297. res = harddisk_db.execute_sql(sql, var)
  298. move_gz(gz_path)
  299. continue
  300. data_start = time.time()
  301. # =============================== | 3. 初始化参数 | ================================
  302. # 默认参数
  303. IMAGE_SHAPE = (631, 1078, 3)
  304. mask_dict = onload_mask("mask/", IMAGE_SHAPE)
  305. all_fish_count_list = []
  306. total_num_list = []
  307. Volume_Process = [[], [], [], [], [], []]
  308. try:
  309. tar = tarfile.open(image_tar)
  310. info_list = tar.getmembers()
  311. s_list, b_list = list_split(info_list)
  312. except:
  313. error_text = "打开压缩包gz失败"
  314. is_abnormal = 1
  315. fish_num = None
  316. sql = "UPDATE package_data SET is_processed = 1 , fish_count = %s,is_abnormal = %s,error_text=%s WHERE id = %s; "
  317. var = (fish_num, is_abnormal, error_text, ID)
  318. res = harddisk_db.execute_sql(sql, var)
  319. move_gz(gz_path)
  320. continue
  321. if len(b_list) < 60:
  322. error_text = f"len(s_list): {len(s_list)}, len(b_list): {len(b_list)}"
  323. is_abnormal = 1
  324. fish_num = None
  325. sql = "UPDATE package_data SET is_processed = 1 , fish_count = %s,is_abnormal = %s,error_text=%s WHERE id = %s; "
  326. var = (fish_num, is_abnormal, error_text, ID)
  327. res = harddisk_db.execute_sql(sql, var)
  328. move_gz(gz_path)
  329. continue
  330. # =============================== | 4. 开始计算 | ================================
  331. image_path = os.path.split(image_tar)[0]
  332. fish_count_list = []
  333. error_num = 0
  334. for image_info_24m in tqdm(b_list):
  335. (image_name2, ext) = os.path.splitext(image_info_24m.name)
  336. f = tar.extractfile(image_info_24m)
  337. img_buffer = f.read()
  338. nparr = np.frombuffer(img_buffer, np.uint8)
  339. try:
  340. image = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
  341. except:
  342. continue
  343. if flag_30to24 == 1:
  344. image = image30to24(image)
  345. elif flag_24to30 == 1:
  346. image = image24to30(image)
  347. # 预测鱼群轮廓
  348. results1 = model(image, conf=0.90)
  349. if results1[0].masks is None:
  350. error_num += 1
  351. print(image_name2,'yolo error')
  352. continue
  353. mask = results1[0].masks.xy
  354. result_tuple = (mask[0].reshape(len(mask[0]), 1, 2).astype(int),)
  355. # 创建一个空白图像,大小与原图像相同
  356. fish_mask = np.zeros_like(image)
  357. # 将掩码绘制到空白图像上
  358. for contour in mask:
  359. cv2.drawContours(fish_mask, [contour.reshape(len(contour), 1, 2).astype(int)], -1, (255, 255, 255), thickness=cv2.FILLED)
  360. # 使用掩码提取鱼的部分
  361. fish_image = cv2.bitwise_and(image, fish_mask)
  362. finall_num = get_fish_ratio_all(fish_image, image_name2, mask_dict, CAGE_LENGTH, fish_size=fishsze, min_size=min_size)
  363. total_num_list.append(finall_num)
  364. mean_value = np.median(total_num_list)
  365. std_dev = np.std(total_num_list)
  366. threshold_max = mean_value + 2 * std_dev
  367. threshold_min = mean_value - 2 * std_dev
  368. if threshold_min < 0:
  369. threshold_min = 0
  370. total_num_list = [
  371. x for x in total_num_list if x > threshold_min and x < threshold_max
  372. ]
  373. if len(total_num_list) < 20:
  374. print(image_name2, "有效数据不足")
  375. error_text = f"len(total_num_list) < 20:{len(total_num_list)}"
  376. is_abnormal = 1
  377. fish_num = None
  378. sql = "UPDATE package_data SET is_processed = 1 , fish_count = %s,is_abnormal = %s,error_text=%s WHERE id = %s; "
  379. var = (fish_num, is_abnormal, error_text, ID)
  380. res = harddisk_db.execute_sql(sql, var)
  381. move_gz(gz_path)
  382. continue
  383. print("这一组情况:")
  384. print(total_num_list)
  385. print("max:{}".format(max(total_num_list)))
  386. print("min:{}".format(min(total_num_list)))
  387. print("mean:{}".format(sum(total_num_list) // len(total_num_list)))
  388. date_off = time.time()
  389. print("花费时间", date_off - data_start)
  390. print("异常图片数量", error_num)
  391. fish_num = int(sum(total_num_list) // len(total_num_list))
  392. is_abnormal = 0
  393. sql = "UPDATE package_data SET is_processed = 1 , fish_count = %s,is_abnormal = %s WHERE id = %s; "
  394. var = (fish_num, is_abnormal, ID)
  395. res = harddisk_db.execute_sql(sql, var)
  396. move_gz(gz_path)