takephoto.c 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430
  1. #include "takephoto.h"
  2. // 模块名称
  3. static const char MODULE_NAME[] = "TakePhoto";
  4. // 相机类型
  5. static unsigned int device_type = MV_GIGE_DEVICE | MV_USB_DEVICE;
  6. // 制造厂商
  7. static const char *manufacturer = "Hikrobot";
  8. // 触发模式
  9. static MV_CAM_TRIGGER_MODE setTriggerMode = MV_TRIGGER_MODE_ON; /// 默认: 打开
  10. // 网口相机
  11. #define GIGE_CAMERA_ANY_VENDOR 1 // 支持任意厂商的网口相机, 否则只支持海康机器人
  12. // 曝光取图
  13. #define AE_WAIT_MAX_MS 180000 // 最大曝光稳定等待的时间, 默认3分钟, 单位: ms
  14. #define AE_EXP_TIME_EPS_US 50 // 曝光时间稳定判定的误差, 防抖&区间, 单位: us
  15. #define AE_EXP_STABLE_FRAMES 4 // 相机处于自动曝光模式时, 累计曝光值稳定的帧数
  16. // 保存照片
  17. static int SavePhoto(HANDLE hCam, MV_FRAME_OUT *pFrame, HKImgType imgType, const char *imgFile);
  18. // 拍照回调
  19. typedef struct
  20. {
  21. HANDLE hCam; // 打开相机的句柄
  22. uint32_t camType; // 相机类型:U、网
  23. char manuName[MAX_LINE_CHARS]; //制造厂商名称
  24. HKImgType saveImgType; // 保存图像的类型
  25. const char *saveImgPath; // 保存图像的路径
  26. bool isExposureAuto; // 自动曝光:是/否
  27. struct timespec expTime0; // 曝光开始的时间
  28. float lastExpTime; // 上次的曝光时长(us)
  29. int expStableCnt; // 连续曝光稳定帧计数
  30. HANDLE hESig; // 任务结束的通知
  31. int rCode; // 任务结束返回值
  32. } PthotoProcCtx;
  33. // 网口相机
  34. // 支持的第三方厂商列表(用海康的SDK操作相机并拍照)
  35. static const char * const gige_3rd_manu_names[] = {"Huaray Technology", "Machine Vision"};
  36. static bool gige_3rd_manu_supported(const char *name)
  37. {
  38. if(0 == GIGE_CAMERA_ANY_VENDOR) return false;
  39. for(int i = 0; i < sizeof(gige_3rd_manu_names)/sizeof(gige_3rd_manu_names[0]); i++)
  40. {
  41. if(xstrcasecmp(name, gige_3rd_manu_names[i]) == 0) return true;
  42. }
  43. return false;
  44. }
  45. static int PhotoProc(unsigned long wParam, unsigned long lParam) // 线程回调函数,执行一次拍照任务
  46. {
  47. PthotoProcCtx *ctx = (PthotoProcCtx *)wParam; MV_FRAME_OUT frame = { 0 }; int ret;
  48. // 1, 触发一次拍照
  49. getp:
  50. ret = (setTriggerMode == MV_TRIGGER_MODE_ON) ? MV_CC_SetCommandValue(ctx->hCam, "TriggerSoftware") : MV_OK;
  51. if(setTriggerMode == MV_TRIGGER_MODE_ON) sw_thrd_delay(1000); // 软触发后, 延时一秒, 等待相机执行一次拍照
  52. // 2, 等待获取图像
  53. if(MV_OK == ret)
  54. {
  55. memset(&frame, 0, sizeof(MV_FRAME_OUT));
  56. ret = MV_CC_GetImageBuffer(ctx->hCam, &frame, 1000);
  57. }
  58. // 3, 导出图像文件
  59. if(MV_OK == ret)
  60. {
  61. sw_log_debug("[%s] +++GetOneFrame+++, Width[%d], Height[%d], FrameNum[%d], FrameLen[%d], PixelType[0x%08X]", MODULE_NAME, \
  62. frame.stFrameInfo.nWidth, frame.stFrameInfo.nHeight, frame.stFrameInfo.nFrameNum, frame.stFrameInfo.nFrameLen, \
  63. (unsigned int)frame.stFrameInfo.enPixelType);
  64. if(ctx->isExposureAuto)
  65. { // 自动曝光模式下, 等待曝光稳定或超时
  66. MVCC_FLOATVALUE stExposureTime = { 0 }; float curExpTime = 0;
  67. ret = MV_CC_GetExposureTime(ctx->hCam, &stExposureTime);
  68. if(MV_OK == ret) curExpTime = stExposureTime.fCurValue;
  69. struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now);
  70. long elapsed = (now.tv_sec - ctx->expTime0.tv_sec)*1000 + (now.tv_nsec-ctx->expTime0.tv_nsec)/(1000*1000); // 计算时间差, 单位: ms
  71. sw_log_debug("[%s] +++GetOneFrame+++, 等待曝光完成, ExposureTime = %.2fus, ElapsedTime = %ldms", MODULE_NAME, curExpTime, elapsed);
  72. if(fabsf(curExpTime - ctx->lastExpTime) <= AE_EXP_TIME_EPS_US) ctx->expStableCnt++;
  73. else ctx->expStableCnt = 0;
  74. if(ctx->expStableCnt < AE_EXP_STABLE_FRAMES && elapsed < AE_WAIT_MAX_MS)
  75. {
  76. MV_CC_FreeImageBuffer(ctx->hCam, &frame);
  77. ctx->lastExpTime = curExpTime; if(0 == ctx->expStableCnt) ctx->expStableCnt = 1;
  78. sw_thrd_delay(1); goto getp; // 继续取图像
  79. }
  80. }
  81. ret = SavePhoto(ctx->hCam, &frame, ctx->saveImgType, ctx->saveImgPath);
  82. MV_CC_FreeImageBuffer(ctx->hCam, &frame);
  83. }
  84. // 4, 控制线程退出
  85. switch(ret)
  86. {
  87. case MV_E_NODATA: // 无数据时, 线程继续运行
  88. sw_log_debug("[%s] +++GetOneFrame+++, 无数据, 继续等待...", MODULE_NAME);
  89. ret = 1; break;
  90. default: // 成功或发生其他错误,线程结束运行
  91. ctx->rCode = ret; sw_signal_give(ctx->hESig); ret = -1; break;
  92. }
  93. return ret;
  94. }
  95. // 单次执行相机拍照, 并保存到文件, 成功返回: 0值, 失败返回:非0值
  96. // "imgType" - 获取图像类型
  97. // "saveImgPath" - 保存的文件名
  98. // "timeout" - 等待超时时间, 单位:秒: < 0 表示无超时;
  99. // 无论超时怎么设置, 首次尝试拍照一定会执行
  100. // , 但时间不确定; 拍照成功、超时或发生错误
  101. // 时会自动结束任务(相机持续无数据的时间).
  102. // "pImgMark" - 输出本次拍照图像的水印信息, 可以设置NULL
  103. int HK_TakePhoto(HKImgType imgType, const char *saveImgPath, int timeout, HKImgMark *pImgMark)
  104. {
  105. int fd; char runDir[MAX_PATH_CHARS] = { 0 }, lockFile[MAX_PATH_CHARS+32] = { 0 };
  106. int ret, index = 0; HKImgMark imgMark = { 0 }; HANDLE hCam = NULL; PthotoProcCtx ctx = { 0 };
  107. MV_CC_DEVICE_INFO_LIST devList = { 0 }; MV_CC_DEVICE_INFO *pDevInfo; MVCC_ENUMVALUE exposureMode = { 0 };
  108. // 1, 占用锁定, 避免同时间拍照
  109. xGetSelfRunningInfo(runDir, NULL);
  110. if(runDir[strlen(runDir)-1] == '/') sprintf(lockFile, "%s%s", runDir, "status/");
  111. else sprintf(lockFile, "%s/%s", runDir, "status/");
  112. if(!sw_dir_exists(lockFile)) sw_dir_create(lockFile);
  113. strcat(lockFile, "hk_takephoto.lock");
  114. fd = open(lockFile, O_CREAT | O_RDWR | __O_CLOEXEC, 0666);
  115. if(-1 == fd) { return -1; }
  116. if(-1 == flock(fd, LOCK_EX | LOCK_NB)) { close(fd); return -2; }
  117. // 2, 查找相机, 获取其设备信息
  118. ret = MV_CC_Initialize();
  119. if(MV_OK != ret)
  120. {
  121. sw_log_error("[%s] 相机SDK初始化失败, errCode=0x%x!!", MODULE_NAME, ret);
  122. goto end_p;
  123. }
  124. ret = MV_CC_EnumDevices(device_type, &devList);
  125. if(MV_OK != ret)
  126. {
  127. sw_log_error("[%s] 枚举相机,执行错误, errCode=0x%x!!", MODULE_NAME, ret);
  128. goto end_p;
  129. }
  130. if(devList.nDeviceNum < 1)
  131. {
  132. ret = -3;
  133. sw_log_error("[%s] 没有相机,数量=%u!!", MODULE_NAME, devList.nDeviceNum);
  134. goto end_p;
  135. }
  136. findp:
  137. pDevInfo = devList.pDeviceInfo[index++];
  138. if(!pDevInfo)
  139. {
  140. ret = -4;
  141. sw_log_error("[%s] unexpected internal error, unable to obtain detailed information about the industrial camera!!", MODULE_NAME);
  142. goto end_p;
  143. }
  144. ctx.manuName[0] = '\0'; if(0) ;
  145. else if(pDevInfo->nTLayerType == MV_USB_DEVICE /*U口相机*/)
  146. {
  147. strcpy(ctx.manuName, (const char *)pDevInfo->SpecialInfo.stUsb3VInfo.chManufacturerName);
  148. ctx.camType = MV_USB_DEVICE;
  149. strcpy(imgMark.camModelName, (const char *)pDevInfo->SpecialInfo.stUsb3VInfo.chModelName);
  150. strcpy(imgMark.camSerialNum, (const char *)pDevInfo->SpecialInfo.stUsb3VInfo.chSerialNumber);
  151. }
  152. else if(pDevInfo->nTLayerType == MV_GIGE_DEVICE/*G口相机*/)
  153. {
  154. strcpy(ctx.manuName, (const char *)pDevInfo->SpecialInfo.stGigEInfo.chManufacturerName);
  155. ctx.camType = MV_GIGE_DEVICE;
  156. strcpy(imgMark.camModelName, (const char *)pDevInfo->SpecialInfo.stGigEInfo.chModelName);
  157. strcpy(imgMark.camSerialNum, (const char *)pDevInfo->SpecialInfo.stGigEInfo.chSerialNumber);
  158. if(xstrcasecmp(ctx.manuName, manufacturer) != 0 && gige_3rd_manu_supported(ctx.manuName))
  159. {
  160. setTriggerMode = MV_TRIGGER_MODE_OFF;
  161. }
  162. }
  163. if(xstrcasecmp(ctx.manuName, manufacturer) != 0 && (ctx.camType != MV_GIGE_DEVICE || !gige_3rd_manu_supported(ctx.manuName)))
  164. {
  165. if(index < devList.nDeviceNum) goto findp;
  166. ret = -5;
  167. sw_log_error("[%s] 没有找到 \"%s\" 的相机!!", MODULE_NAME, manufacturer);
  168. goto end_p;
  169. }
  170. // 3, 打开相机, 设置其触发模式
  171. ret = MV_CC_CreateHandleWithoutLog(&hCam, pDevInfo);
  172. if(MV_OK == ret) ret = MV_CC_OpenDevice(hCam, MV_ACCESS_Exclusive, 0);
  173. if(MV_OK == ret && pDevInfo->nTLayerType == MV_USB_DEVICE /*U口相机*/)
  174. {
  175. ret = MV_USB_SetTransferWays(hCam, 1); // 设置传输通道个数(RTU硬件资源有限, 需要降低通道数)
  176. }
  177. if(MV_OK == ret && pDevInfo->nTLayerType == MV_GIGE_DEVICE/*G口相机*/)
  178. { // 探测网络最佳包大小(只对GigE相机有效)
  179. int size = MV_CC_GetOptimalPacketSize(hCam);
  180. if(size > 0) ret = MV_CC_SetIntValueEx(hCam, "GevSCPSPacketSize", size);
  181. else ret = -6;
  182. }
  183. if(MV_OK != ret)
  184. {
  185. sw_log_error("[%s] 打开相机时发生错误, errCode=0x%x!!", MODULE_NAME, ret);
  186. goto end_p;
  187. }
  188. ret = MV_CC_SetEnumValue(hCam, "TriggerMode", setTriggerMode); // 设置触发模式
  189. if(MV_OK == ret && setTriggerMode == MV_TRIGGER_MODE_ON) ret = MV_CC_SetEnumValue(hCam, "TriggerSource", MV_TRIGGER_SOURCE_SOFTWARE); // 设置软件触发
  190. if(MV_OK != ret)
  191. {
  192. sw_log_error("[%s] 设置相机时发生错误, errCode=0x%x!!", MODULE_NAME, ret);
  193. goto end_p;
  194. }
  195. // 4, 开始拍照, 等待完成后输出
  196. ret = MV_CC_GetExposureAutoMode(hCam, &exposureMode);
  197. if(MV_OK != ret)
  198. {
  199. sw_log_error("[%s] 获取曝光模式时出错, errCode=0x%x!!", MODULE_NAME, ret);
  200. goto end_p;
  201. }
  202. if(exposureMode.nCurValue != MV_EXPOSURE_AUTO_MODE_OFF) { ctx.isExposureAuto = true; clock_gettime(CLOCK_MONOTONIC, &ctx.expTime0); }
  203. else ctx.isExposureAuto = false;
  204. ret = MV_CC_StartGrabbing(hCam);
  205. if(MV_OK != ret)
  206. {
  207. sw_log_error("[%s] 相机取流时发生错误, errCode=0x%x!!", MODULE_NAME, ret);
  208. goto end_p;
  209. }
  210. ctx.hCam = hCam;
  211. ctx.saveImgType = imgType;
  212. ctx.saveImgPath = saveImgPath;
  213. ctx.hESig = sw_signal_create();
  214. if(!ctx.hESig)
  215. {
  216. ret = -7;
  217. sw_log_error("[%s] SIG信号量创建失败!!", MODULE_NAME);
  218. goto end_p;
  219. }
  220. ctx.rCode = MV_OK;
  221. HANDLE hThrd = sw_thrd_create("PhotoProc", THREAD_DEFAULT_PRIORITY, THREAD_DEFAULT_STACK_SIZE, PhotoProc, (unsigned long)&ctx, 0);
  222. if(!hThrd)
  223. {
  224. ret = -8;
  225. sw_signal_destroy(ctx.hESig);
  226. sw_log_error("[%s] 拍照线程-创建失败!!", MODULE_NAME);
  227. goto end_p;
  228. }
  229. sw_thrd_resume(hThrd);
  230. ret = sw_signal_wait(ctx.hESig, timeout*1000); // 阻塞等待拍照任务结束或超时
  231. if(0 == ret) ret = ctx.rCode;
  232. else
  233. {
  234. ret = -9;
  235. sw_log_error("[%s] 拍照过程-等待超时!!", MODULE_NAME);
  236. }
  237. sw_thrd_destroy(hThrd, WAITTHRD_SAFEEXIT_TIMEOUT);
  238. sw_signal_destroy(ctx.hESig);
  239. // 5, 成功拍照, 输出相机的信息
  240. if(MV_OK == ret)
  241. {
  242. MVCC_FLOATVALUE fv = { 0 };
  243. MV_CC_GetFloatValue(hCam, "ExposureTime", &fv);
  244. imgMark.imgExposureTime = fv.fCurValue;
  245. if(pImgMark) memcpy(pImgMark, &imgMark, sizeof(HKImgMark));
  246. }
  247. // 6, 任务结束, 释放相关的资源
  248. end_p:
  249. if(hCam)
  250. {
  251. MV_CC_StopGrabbing(hCam);
  252. MV_CC_CloseDevice(hCam);
  253. MV_CC_DestroyHandle(hCam);
  254. }
  255. MV_CC_Finalize();
  256. flock(fd, LOCK_UN);
  257. close(fd);
  258. sw_file_delete(lockFile);
  259. return ret;
  260. }
  261. // 保存照片
  262. static int SavePhoto(HANDLE hCam, MV_FRAME_OUT *pFrame, HKImgType imgType, const char *imgFile)
  263. {
  264. MV_SAVE_IMAGE_PARAM_EX3 saveParams = { 0 }; MVCC_INTVALUE_EX iv;
  265. char *filename1 = (char *)imgFile, *filename2 = NULL; int ret; char ext[5];
  266. if(!hCam || !pFrame || !filename1 || strlen(filename1) <= 0) return -15;
  267. switch(imgType)
  268. {
  269. case IMG_TYPE_BMP: // 保存为.bmp格式
  270. imgType = MV_Image_Bmp;
  271. strcpy(ext, ".bmp");
  272. break;
  273. case IMG_TYPE_JPG: // 保存为.jpg格式
  274. imgType = MV_Image_Jpeg;
  275. strcpy(ext, ".jpg");
  276. break;
  277. default: return -16;
  278. }
  279. ret = MV_CC_GetIntValueEx(hCam, "PayloadSize", &iv);
  280. if(MV_OK != ret) return ret;
  281. saveParams.pData = pFrame->pBufAddr;
  282. saveParams.nDataLen = pFrame->stFrameInfo.nFrameLen;
  283. saveParams.enPixelType = pFrame->stFrameInfo.enPixelType;
  284. saveParams.nHeight = pFrame->stFrameInfo.nHeight;
  285. saveParams.nWidth = pFrame->stFrameInfo.nWidth;
  286. saveParams.enImageType = imgType;
  287. saveParams.nJpgQuality = 90; // JPG编码质量(50-99], 其它格式无效, 影响JPG图像文件大小
  288. saveParams.iMethodValue = 1;
  289. if(imgType == IMG_TYPE_JPG) saveParams.nBufferSize = (unsigned int)(iv.nCurValue * 1);
  290. else saveParams.nBufferSize = (unsigned int)(iv.nCurValue * 4);
  291. saveParams.pImageBuffer = (unsigned char *)sw_heap_malloc(saveParams.nBufferSize);
  292. if(!saveParams.pImageBuffer) return -17;
  293. ret = MV_CC_SaveImageEx3(hCam, &saveParams);
  294. if(MV_OK != ret) goto end_p;
  295. if(!xstrcasestr(filename1, "right", ext))
  296. {
  297. filename2 = (char *)sw_heap_malloc(strlen(filename1) +sizeof(ext));
  298. if(filename2) sprintf(filename2, "%s%s", filename1, ext);
  299. else { ret = -18; goto end_p; }
  300. ret = sw_file_update(filename2, "wb", (const char *)saveParams.pImageBuffer, saveParams.nImageLen);
  301. sw_heap_free(filename2);
  302. }
  303. else
  304. {
  305. ret = sw_file_update(filename1, "wb", (const char *)saveParams.pImageBuffer, saveParams.nImageLen);
  306. }
  307. if(ret == saveParams.nImageLen) ret = MV_OK;
  308. else ret = -19;
  309. end_p:
  310. sw_heap_free(saveParams.pImageBuffer);
  311. return ret;
  312. }
  313. // 获取系统当前USBFS内存大小(MB), 成功返回: >=0, 失败返回: <0值
  314. int HK_GetSysUsbfsMemCurrentSize()
  315. {
  316. const char *usbfsFile = "/sys/module/usbcore/parameters/usbfs_memory_mb";
  317. char buf[MAX_LINE_CHARS]; int val = -1;
  318. FILE *fp = fopen(usbfsFile, "r");
  319. if(fp)
  320. {
  321. if(fgets(buf, sizeof(buf), fp)) sscanf(buf, "%d", &val);
  322. fclose(fp);
  323. }
  324. return val;
  325. }
  326. // 设置系统新的USBFS内存大小(MB), 成功返回: 0值, 失败返回: <0值
  327. int HK_SetSysUsbfsMemSize(int val)
  328. {
  329. const char *usbfsFile = "/sys/module/usbcore/parameters/usbfs_memory_mb";
  330. char buf[MAX_LINE_CHARS]; sprintf(buf, "%d\n", val);
  331. if(val >= 0 && sw_file_update(usbfsFile, "w", buf, strlen(buf)) == strlen(buf)) return 0;
  332. else return -1;
  333. }
  334. // 获取当前已连接的海康相机的数量, 成功返回: >=0, 失败返回: <0值
  335. int HK_GetCameraCount()
  336. {
  337. MV_CC_DEVICE_INFO_LIST devList = { 0 };
  338. int ret;
  339. ret = MV_CC_Initialize();
  340. if(MV_OK != ret)
  341. {
  342. sw_log_error("[%s] 相机SDK初始化失败, errCode=0x%x!!", MODULE_NAME, ret);
  343. goto end_p;
  344. }
  345. ret = MV_CC_EnumDevices(device_type, &devList);
  346. if(MV_OK != ret)
  347. {
  348. sw_log_error("[%s] 枚举相机,执行错误, errCode=0x%x!!", MODULE_NAME, ret);
  349. ret = -1; goto end_p;
  350. }
  351. ret = devList.nDeviceNum;
  352. if(ret > 0)
  353. {
  354. int index = 0, cnt = 0; MV_CC_DEVICE_INFO *pDevInfo; char name[MAX_LINE_CHARS];
  355. findp:
  356. pDevInfo = devList.pDeviceInfo[index++];
  357. name[0] = '\0'; if(0) ;
  358. else if(pDevInfo && pDevInfo->nTLayerType == MV_USB_DEVICE /*U口相机*/) strcpy(name, (const char *)pDevInfo->SpecialInfo.stUsb3VInfo.chManufacturerName);
  359. else if(pDevInfo && pDevInfo->nTLayerType == MV_GIGE_DEVICE/*G口相机*/) strcpy(name, (const char *)pDevInfo->SpecialInfo.stGigEInfo.chManufacturerName) ;
  360. if(xstrcasecmp(name, manufacturer) == 0 || (pDevInfo->nTLayerType == MV_GIGE_DEVICE && gige_3rd_manu_supported(name))) cnt++;
  361. if(index < devList.nDeviceNum) goto findp;
  362. else ret = cnt;
  363. }
  364. end_p:
  365. MV_CC_Finalize();
  366. return ret;
  367. }