AiController.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454
  1. import base64
  2. import json
  3. import logging
  4. import os
  5. import threading
  6. import time
  7. import apns2
  8. import boto3
  9. import jpush
  10. from boto3.session import Session
  11. from django.views.generic.base import View
  12. from pyfcm import FCMNotification
  13. from AnsjerPush.Config.aiConfig import LABEL_DICT, AI_IDENTIFICATION_TAGS_DICT
  14. from AnsjerPush.config import APNS_MODE, APNS_CONFIG, BASE_DIR, \
  15. JPUSH_CONFIG, FCM_CONFIG, ACCESS_KEY_ID, SECRET_ACCESS_KEY, REGION_NAME, PUSH_BUCKET
  16. from Model.models import UidPushModel, AiService
  17. from Object.ETkObject import ETkObject
  18. from Object.MergePic import ImageProcessing
  19. from Object.ResponseObject import ResponseObject
  20. from Object.utils import LocalDateTimeUtil
  21. from Service.CommonService import CommonService
  22. from Service.EquipmentInfoService import EquipmentInfoService
  23. class AiView(View):
  24. def get(self, request, *args, **kwargs):
  25. request.encoding = 'utf-8'
  26. operation = kwargs.get('operation')
  27. return self.validation(request.GET, operation)
  28. def post(self, request, *args, **kwargs):
  29. request.encoding = 'utf-8'
  30. operation = kwargs.get('operation')
  31. return self.validation(request.POST, operation)
  32. def validation(self, request_dict, operation):
  33. response = ResponseObject()
  34. if operation == 'identification': # ai识别推送
  35. return self.identification(request_dict, response)
  36. else:
  37. return response.json(414)
  38. def identification(self, request_dict, response):
  39. """
  40. ai识别推送
  41. @param request_dict: 请求数据
  42. @request_dict etk: uid token
  43. @request_dict n_time: 设备的当前时间
  44. @request_dict channel: 通道
  45. @request_dict fileOne: 图片一
  46. @request_dict fileTwo: 图片二
  47. @request_dict fileThree: 图片三
  48. @param response: 响应
  49. @return: response
  50. """
  51. etk = request_dict.get('etk', None)
  52. n_time = request_dict.get('n_time', None)
  53. channel = request_dict.get('channel', '1')
  54. file_one = request_dict.get('fileOne', None)
  55. file_two = request_dict.get('fileTwo', None)
  56. file_three = request_dict.get('fileThree', None)
  57. if not all([etk, n_time]):
  58. return response.json(444)
  59. # 解密etk并判断uid长度
  60. eto = ETkObject(etk)
  61. uid = eto.uid
  62. logger = logging.getLogger('info')
  63. logger.info('---进入ai识别推送接口--- etk:{}, uid:{}'.format(etk, uid))
  64. receive_time = int(time.time())
  65. file_list = [file_one, file_two, file_three]
  66. # 查询设备是否有使用中的ai服务
  67. ai_service_qs = AiService.objects.filter(uid=uid, detect_status=1, use_status=1, endTime__gt=receive_time). \
  68. values('detect_group')
  69. if not ai_service_qs.exists():
  70. return response.json(173)
  71. detect_group = ai_service_qs[0]['detect_group']
  72. try:
  73. dir_path = os.path.join(BASE_DIR, 'static/ai/' + uid + '/' + str(n_time))
  74. if not os.path.exists(dir_path):
  75. os.makedirs(dir_path)
  76. file_path_list = []
  77. for i, val in enumerate(file_list):
  78. val = val.replace(' ', '+')
  79. val = base64.b64decode(val)
  80. file_path = "{dir_path}/{n_time}_{i}.jpg".format(dir_path=dir_path, n_time=n_time, i=i)
  81. file_path_list.append(file_path)
  82. with open(file_path, 'wb') as f:
  83. f.write(val)
  84. f.close()
  85. image_size = 0 # 每张小图片的大小,等于0是按原图大小进行合并
  86. image_row = 1 # 合并成一张图后,一行有几个小图
  87. ImageProcessingObj = ImageProcessing(dir_path, image_size, image_row)
  88. image_info_dict = ImageProcessing.merge_images(ImageProcessingObj)
  89. photo = open(dir_path + '.jpg', 'rb') # 打开合成图
  90. # 识别合成图片
  91. maxLabels = 50 # 最大标签
  92. minConfidence = 80 # 置信度
  93. client = boto3.client(
  94. 'rekognition',
  95. aws_access_key_id='AKIA2E67UIMD6JD6TN3J',
  96. aws_secret_access_key='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',
  97. region_name='us-east-1')
  98. # doc: https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/rekognition.html#Rekognition.Client.detect_labels
  99. rekognition_res = client.detect_labels(
  100. Image={'Bytes': photo.read()},
  101. MaxLabels=maxLabels,
  102. MinConfidence=minConfidence)
  103. photo.close()
  104. if rekognition_res['ResponseMetadata']['HTTPStatusCode'] != 200:
  105. return response.json(5)
  106. label_dict = self.handle_rekognition_res(detect_group, rekognition_res, image_info_dict)
  107. if not label_dict['label_list']:
  108. # 需要删除图片
  109. # photo.close()
  110. # self.del_path(os.path.join(BASE_DIR, 'static/ai/' + uid))
  111. return response.json(0)
  112. event_type = label_dict['event_type']
  113. label_str = ','.join(label_dict['label_list'])
  114. new_bounding_box_dict = label_dict['new_bounding_box_dict']
  115. # 上传缩略图到s3
  116. file_dict = {}
  117. for i, val in enumerate(file_path_list):
  118. # 封面图
  119. file_dict[val] = '{}/{}/{}_{}.jpeg'.format(uid, channel, n_time, i)
  120. upload_images_thread = threading.Thread(target=self.upload_images, args=(file_dict, dir_path))
  121. upload_images_thread.start()
  122. # 存储消息以及推送
  123. is_st = 3 # 多图
  124. # 查询推送数据
  125. uid_push_qs = UidPushModel.objects.filter(uid_set__uid=uid). \
  126. values('token_val', 'app_type', 'appBundleId', 'm_code', 'push_type', 'userID_id',
  127. 'userID__NickName',
  128. 'lang', 'm_code', 'tz', 'uid_set__nickname', 'uid_set__detect_interval',
  129. 'uid_set__detect_group',
  130. 'uid_set__channel')
  131. if not uid_push_qs.exists():
  132. return response.json(173)
  133. uid_push_list = []
  134. for qs in uid_push_qs:
  135. uid_push_list.append(qs)
  136. nickname = uid_push_list[0]['uid_set__nickname']
  137. if not nickname:
  138. nickname = uid
  139. eq_list = []
  140. userID_ids = []
  141. local_date_time = ''
  142. for up in uid_push_list:
  143. push_type = up['push_type']
  144. appBundleId = up['appBundleId']
  145. token_val = up['token_val']
  146. lang = up['lang']
  147. tz = up['tz']
  148. if tz is None or tz == '':
  149. tz = 0
  150. local_date_time = CommonService.get_now_time_str(n_time=n_time, tz=tz, lang='cn')
  151. logger.info('----AI消息存库{},{},{}'.format(uid, local_date_time, tz))
  152. local_date_time = local_date_time[0:10]
  153. # 以下是存库
  154. userID_id = up["userID_id"]
  155. if userID_id not in userID_ids:
  156. now_time = int(time.time())
  157. eq_list.append(EquipmentInfoService.get_equipment_info_obj(
  158. local_date_time,
  159. device_user_id=userID_id,
  160. event_time=n_time,
  161. event_type=event_type,
  162. device_uid=uid,
  163. device_nick_name=nickname,
  164. channel=channel,
  165. alarm='检查到{} \tChannel:{}'.format(label_str, channel),
  166. is_st=is_st,
  167. receive_time=receive_time,
  168. add_time=now_time,
  169. storage_location=2,
  170. border_coords=json.dumps(new_bounding_box_dict)
  171. ))
  172. userID_ids.append(userID_id)
  173. # 推送标题
  174. msg_title = self.get_msg_title(appBundleId=appBundleId, nickname=nickname)
  175. # 推送内容
  176. msg_text = self.get_msg_text(channel=channel, n_time=n_time, lang=lang, tz=tz, label_list=label_str)
  177. kwargs = {
  178. 'uid': uid,
  179. 'channel': channel,
  180. 'event_type': event_type,
  181. 'n_time': n_time,
  182. 'appBundleId': appBundleId,
  183. 'token_val': token_val,
  184. 'msg_title': msg_title,
  185. 'msg_text': msg_text,
  186. }
  187. try:
  188. # 推送消息
  189. if push_type == 0: # ios apns
  190. self.do_apns(**kwargs)
  191. elif push_type == 1: # android gcm
  192. self.do_fcm(**kwargs)
  193. elif push_type == 2: # android jpush
  194. self.do_jpush(**kwargs)
  195. except Exception as e:
  196. logger.info(
  197. "errLine={errLine}, errMsg={errMsg}".format(errLine=e.__traceback__.tb_lineno, errMsg=repr(e)))
  198. continue
  199. # 分表批量存储
  200. if eq_list and len(eq_list) > 0:
  201. logger.info("AI存库中........")
  202. week = LocalDateTimeUtil.date_to_week(local_date_time)
  203. result = EquipmentInfoService.equipment_info_bulk_create(week, eq_list)
  204. logger.info("-.-存库结果{}".format(result))
  205. return response.json(0)
  206. except Exception as e:
  207. print(e)
  208. data = {
  209. 'errLine': e.__traceback__.tb_lineno,
  210. 'errMsg': repr(e)
  211. }
  212. return response.json(48, data)
  213. @staticmethod
  214. def handle_rekognition_res(detect_group, rekognition_res, image_info_dict):
  215. """
  216. 处理识别结果,匹配检测类型,并且返回标签坐标位置信息
  217. @param detect_group: 检测类型
  218. @param rekognition_res: 识别响应
  219. @param image_info_dict: 合成的图片信息
  220. @return: label_dict
  221. """
  222. logger = logging.getLogger('info')
  223. labels = rekognition_res['Labels']
  224. logger.info('--------识别到的标签-------:{}'.format(labels))
  225. label_name = []
  226. label_list = []
  227. # 找出识别的所有标签
  228. for label in labels:
  229. label_name.append(label['Name'])
  230. for Parents in label['Parents']:
  231. label_name.append(Parents['Name'])
  232. logger.info('------标签名------:{}'.format(label_name))
  233. # 删除用户没有选择的ai识别类型, 并且得出最终识别结果
  234. user_detect_list = detect_group.split(',')
  235. user_detect_list = [i.strip() for i in user_detect_list]
  236. conform_label_list = []
  237. conform_detect_group = set()
  238. for key, label_type_val in LABEL_DICT.items():
  239. if key in user_detect_list:
  240. for label in label_type_val:
  241. if label in label_name:
  242. conform_detect_group.add(key)
  243. conform_label_list.append(label)
  244. # 找出标签边框线位置信息
  245. bounding_box_list = []
  246. for label in labels:
  247. if label['Name'] in conform_label_list:
  248. for label_instance in label['Instances']:
  249. bounding_box_list.append(label_instance['BoundingBox'])
  250. # 找出边框位置信息对应的单图位置并重新计算位置比
  251. merge_image_height = image_info_dict['height']
  252. single_height = merge_image_height // image_info_dict['num']
  253. new_bounding_box_dict = {
  254. 'file_0': [],
  255. 'file_1': [],
  256. 'file_2': []
  257. }
  258. for k, val in enumerate(bounding_box_list):
  259. bounding_box_top = merge_image_height * val['Top']
  260. # 找出当前边框属于哪张图片范围
  261. box_dict = {}
  262. for i in range(image_info_dict['num']):
  263. top_min = i * single_height
  264. top_max = (i + 1) * single_height
  265. if bounding_box_top >= top_min and bounding_box_top <= top_max:
  266. box_dict['Width'] = val['Width']
  267. box_dict['Height'] = merge_image_height * val['Height'] / single_height
  268. # 减去前i张图片的高度
  269. box_dict['Top'] = ((merge_image_height * val['Top']) - (i * single_height)) / single_height
  270. box_dict['Left'] = val['Left']
  271. new_bounding_box_dict['file_{i}'.format(i=i)].append(box_dict)
  272. # 组织返回数据
  273. if not conform_detect_group: # 没有识别到符合的标签
  274. event_type = ''
  275. label_list = []
  276. else:
  277. conform_detect_group = list(conform_detect_group)
  278. if len(conform_detect_group) > 1:
  279. conform_detect_group.sort()
  280. # 集成识别标签
  281. for label_key in conform_detect_group:
  282. label_list.append(AI_IDENTIFICATION_TAGS_DICT[label_key])
  283. event_type = ''.join(conform_detect_group) # 组合类型
  284. else:
  285. label_list.append(AI_IDENTIFICATION_TAGS_DICT[conform_detect_group[0]])
  286. event_type = conform_detect_group[0]
  287. logger.info('------conform_detect_group------ {}'.format(conform_detect_group))
  288. label_dict = {
  289. 'event_type': event_type,
  290. 'label_list': label_list,
  291. 'new_bounding_box_dict': new_bounding_box_dict
  292. }
  293. logger.info('------label_dict------ {}'.format(label_dict))
  294. return label_dict
  295. @staticmethod
  296. def upload_images(file_dict, dir_path):
  297. """
  298. 上传图片
  299. @param file_dict: S3图片路径
  300. @param dir_path: 本地图片路径
  301. @return: boolean
  302. """
  303. try:
  304. s3 = Session(
  305. aws_access_key_id=ACCESS_KEY_ID,
  306. aws_secret_access_key=SECRET_ACCESS_KEY,
  307. region_name=REGION_NAME
  308. ).resource('s3')
  309. for file_path, upload_path in file_dict.items():
  310. upload_data = open(file_path, 'rb')
  311. s3.Bucket(PUSH_BUCKET).put_object(Key=upload_path, Body=upload_data)
  312. # 删除图片
  313. CommonService.del_path(dir_path)
  314. CommonService.del_path(dir_path + '.jpg')
  315. return True
  316. except Exception as e:
  317. print(repr(e))
  318. return False
  319. def get_msg_title(self, appBundleId, nickname):
  320. package_title_config = {
  321. 'com.ansjer.customizedd_a': 'DVS',
  322. 'com.ansjer.zccloud_a': 'ZosiSmart',
  323. 'com.ansjer.zccloud_ab': '周视',
  324. 'com.ansjer.adcloud_a': 'ADCloud',
  325. 'com.ansjer.adcloud_ab': 'ADCloud',
  326. 'com.ansjer.accloud_a': 'ACCloud',
  327. 'com.ansjer.loocamccloud_a': 'Loocam',
  328. 'com.ansjer.loocamdcloud_a': 'Anlapus',
  329. 'com.ansjer.customizedb_a': 'COCOONHD',
  330. 'com.ansjer.customizeda_a': 'Guardian365',
  331. 'com.ansjer.customizedc_a': 'PatrolSecure',
  332. }
  333. if appBundleId in package_title_config.keys():
  334. return package_title_config[appBundleId] + '(' + nickname + ')'
  335. else:
  336. return nickname
  337. def get_msg_text(self, channel, n_time, lang, tz, label_list):
  338. n_date = CommonService.get_now_time_str(n_time=n_time, tz=tz, lang=lang)
  339. if lang == 'cn':
  340. msg = '摄像头AI识别到了{}'.format(label_list)
  341. send_text = '{msg} 通道:{channel} 日期:{date}'.format(msg=msg, channel=channel, date=n_date)
  342. else:
  343. msg = 'Camera AI recognizes {}'.format(label_list)
  344. send_text = '{msg} channel:{channel} date:{date}'.format(msg=msg, channel=channel, date=n_date)
  345. return send_text
  346. def do_jpush(self, uid, channel, appBundleId, token_val, event_type, n_time, msg_title, msg_text):
  347. app_key = JPUSH_CONFIG[appBundleId]['Key']
  348. master_secret = JPUSH_CONFIG[appBundleId]['Secret']
  349. # 此处换成各自的app_key和master_secre
  350. _jpush = jpush.JPush(app_key, master_secret)
  351. push = _jpush.create_push()
  352. push.audience = jpush.registration_id(token_val)
  353. push_data = {"alert": "Motion ", "event_time": n_time, "event_type": event_type, "msg": "",
  354. "received_at": n_time, "sound": "sound.aif", "uid": uid, "zpush": "1", "channel": channel}
  355. android = jpush.android(alert=msg_text, priority=1, style=1, alert_type=7,
  356. big_text=msg_text, title=msg_title,
  357. extras=push_data)
  358. push.notification = jpush.notification(android=android)
  359. push.platform = jpush.all_
  360. res = push.send()
  361. print(res)
  362. return res.status_code
  363. def do_fcm(self, uid, channel, appBundleId, token_val, event_type, n_time, msg_title, msg_text):
  364. try:
  365. serverKey = FCM_CONFIG[appBundleId]
  366. push_service = FCMNotification(api_key=serverKey)
  367. data = {"alert": "Motion ", "event_time": n_time, "event_type": event_type, "msg": "",
  368. "received_at": n_time, "sound": "sound.aif", "uid": uid, "zpush": "1", "channel": channel}
  369. result = push_service.notify_single_device(registration_id=token_val, message_title=msg_title,
  370. message_body=msg_text, data_message=data,
  371. extra_kwargs={
  372. 'default_vibrate_timings': True,
  373. 'default_sound': True,
  374. 'default_light_settings': True
  375. })
  376. print('fcm push ing')
  377. print(result)
  378. return result
  379. except Exception as e:
  380. return 'serverKey abnormal'
  381. def do_apns(self, uid, channel, appBundleId, token_val, event_type, n_time, msg_title, msg_text):
  382. logger = logging.getLogger('info')
  383. logger.info("进来do_apns函数了")
  384. logger.info(token_val)
  385. logger.info(APNS_MODE)
  386. logger.info(os.path.join(BASE_DIR, APNS_CONFIG[appBundleId]['pem_path']))
  387. try:
  388. cli = apns2.APNSClient(mode=APNS_MODE,
  389. client_cert=os.path.join(BASE_DIR, APNS_CONFIG[appBundleId]['pem_path']))
  390. push_data = {"alert": "Motion ", "event_time": n_time, "event_type": event_type, "msg": "",
  391. "received_at": n_time, "sound": "", "uid": uid, "zpush": "1", "channel": channel}
  392. alert = apns2.PayloadAlert(body=msg_text, title=msg_title)
  393. payload = apns2.Payload(alert=alert, custom=push_data, sound="default")
  394. n = apns2.Notification(payload=payload, priority=apns2.PRIORITY_LOW)
  395. res = cli.push(n=n, device_token=token_val, topic=appBundleId)
  396. if res.status_code == 200:
  397. return res.status_code
  398. else:
  399. logger.info('apns push fail')
  400. logger.info(res.reason)
  401. return res.status_code
  402. except (ValueError, ArithmeticError):
  403. return 'The program has a numeric format exception, one of the arithmetic exceptions'
  404. except Exception as e:
  405. print(repr(e))
  406. logger.info(repr(e))
  407. return repr(e)