takephoto.c 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449
  1. #include "takephoto.h"
  2. #include "stb_image_write.h"
  3. // 模块名称
  4. static const char MODULE_NAME[] = "HrTakePhoto";
  5. // 相机类型
  6. static unsigned int device_type = interfaceTypeGige | interfaceTypeUsb3;
  7. // 制造厂商
  8. static const char * const manufacturer = "Huaray Technology";
  9. // 触发模式
  10. static const char * const setTriggerMode = "Off"; /// 默认: "Off", 可选: "On"
  11. // 曝光取图
  12. #define AE_WAIT_MAX_MS 180000 // 最大曝光稳定等待的时间, 默认3分钟, 单位: ms
  13. #define AE_EXP_TIME_EPS_US 50 // 曝光时间稳定判定的误差, 防抖&区间, 单位: us
  14. #define AE_EXP_STABLE_FRAMES 4 // 相机处于自动曝光模式时, 累计曝光值稳定的帧数
  15. static int waitAEDone = 1; // 是否等待自动曝光完成: "1"-等待(默认), "0"-不等待//
  16. // 保存照片
  17. static int SavePhoto(HANDLE hCam, IMV_Frame *pFrame, DHImgType imgType, const char *imgPath);
  18. // 拍照环境
  19. typedef struct
  20. {
  21. HANDLE hCam; // 打开相机的句柄
  22. uint32_t camType; // 相机类型:U、网
  23. char manuName[MAX_LINE_CHARS]; //制造厂商名称
  24. DHImgType 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. void *pUser; // 可选的用户数据
  33. } PthotoProcCtx;
  34. // 非标准的"Huaray Technology"制造商名称, 但也可视为华睿的
  35. static const char * const huaray_manu_aliases[] = { "Machine Vision", NULL };
  36. static bool is_huaray_manu_alias(const char *name)
  37. {
  38. for(int i = 0; huaray_manu_aliases[i] != NULL; i++)
  39. {
  40. if(xstrcasecmp(name, huaray_manu_aliases[i]) == 0) return true;
  41. }
  42. return false;
  43. }
  44. static void OnFrameReceived(IMV_Frame *pFrame, void *pUser) // 数据帧的回调, 完成一次拍照任务
  45. {
  46. PthotoProcCtx *ctx = (PthotoProcCtx *)pUser; int ret = IMV_OK;
  47. if(NULL == pFrame) return;
  48. // 1, 打印该帧信息
  49. sw_log_debug("[%s] +++GetOneFrame+++, Width[%u], Height[%u], FrameNum[%llu], FrameLen[%u], PixelType[0x%08X]", MODULE_NAME,
  50. pFrame->frameInfo.width, pFrame->frameInfo.height, pFrame->frameInfo.blockId, pFrame->frameInfo.size,
  51. (unsigned int)pFrame->frameInfo.pixelFormat);
  52. // 2, 自动曝光模式
  53. if(ctx->isExposureAuto && waitAEDone != 0)
  54. {
  55. double curExpTime = 0.0;
  56. ret = IMV_GetDoubleFeatureValue(ctx->hCam, "ExposureTime", &curExpTime);
  57. if(IMV_OK != ret) goto end_p;
  58. struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now);
  59. long elapsed = (now.tv_sec - ctx->expTime0.tv_sec)*1000 + (now.tv_nsec-ctx->expTime0.tv_nsec)/(1000*1000); // 计算时间差, 单位: ms
  60. sw_log_debug("[%s] +++GetOneFrame+++, 等待曝光完成, ExposureTime = %.2fus, ElapsedTime = %ldms", MODULE_NAME, curExpTime, elapsed);
  61. if(fabsf(curExpTime - ctx->lastExpTime) <= AE_EXP_TIME_EPS_US) ctx->expStableCnt++;
  62. else ctx->expStableCnt = 0;
  63. if(ctx->expStableCnt < AE_EXP_STABLE_FRAMES && elapsed < AE_WAIT_MAX_MS)
  64. { // 曝光未稳定, 也未超时, 则继续等下一帧
  65. ctx->lastExpTime = curExpTime; if(0 == ctx->expStableCnt) ctx->expStableCnt = 1;
  66. return;
  67. }
  68. }
  69. // 3, 导出图像文件
  70. if(ctx->pUser) { sw_thrd_destroy(ctx->pUser, WAITTHRD_SAFEEXIT_TIMEOUT); ctx->pUser = NULL; }
  71. ret = SavePhoto(ctx->hCam, pFrame, ctx->saveImgType, ctx->saveImgPath);
  72. // 4, 设置拍照完成
  73. end_p:
  74. ctx->rCode = ret; sw_signal_give(ctx->hESig);
  75. }
  76. static int FrameSoftTrigger(unsigned long wParam, unsigned long lParam) // 线程回调函数, 触发相机拍照
  77. {
  78. PthotoProcCtx *ctx = (PthotoProcCtx *)wParam;
  79. void *hThrd = ctx->pUser; int slot_ms = 100, tick_ms, ret;
  80. while(sw_thrd_isAlive(hThrd))
  81. {
  82. sw_log_debug("[%s] 触发一次拍照", MODULE_NAME);
  83. ret = IMV_ExecuteCommandFeature(ctx->hCam, "TriggerSoftware");
  84. if(IMV_OK != ret) { sw_log_error("[%s] 触发拍照失败, errCode=%d!!", MODULE_NAME, ret); break; }
  85. else tick_ms = 0;
  86. while(sw_thrd_isAlive(hThrd) && tick_ms < 1000) { sw_thrd_delay(slot_ms); tick_ms += slot_ms; }
  87. }
  88. return -1;
  89. }
  90. // 单次拍照时, 如果相机处于自动曝光模式, 是否等待曝光稳定后再拍照
  91. int DH_SetWaitAEDoneForTakePhoto(int waitAE)
  92. {
  93. waitAEDone = (waitAE != 0);
  94. return 0;
  95. }
  96. // 单次执行相机拍照, 并保存到文件, 成功返回: 0值, 失败返回:非0值
  97. // "imgType" - 获取图像类型
  98. // "saveImgPath" - 保存的文件名
  99. // "timeout" - 等待超时时间, 单位:秒: < 0 表示无超时;
  100. // 无论超时怎么设置, 首次尝试拍照一定会执行
  101. // , 但时间不确定; 拍照成功、超时或发生错误
  102. // 时会自动结束任务(相机持续无数据的时间).
  103. // "pImgMark" - 输出本次拍照图像的水印信息, 可以设置NULL
  104. int DH_TakePhoto(DHImgType imgType, const char *saveImgPath, int timeout, DHImgMark *pImgMark)
  105. {
  106. int fd; char runDir[MAX_PATH_CHARS] = { 0 }, lockFile[MAX_PATH_CHARS+32] = { 0 };
  107. int ret, index = 0; DHImgMark imgMark = { 0 }; HANDLE hCam = NULL; PthotoProcCtx ctx = { 0 };
  108. IMV_DeviceList devList = { 0 }; IMV_DeviceInfo *pDevInfo = NULL; IMV_String exposureMode = { 0 };
  109. // 1, 占用锁定, 避免同时间拍照
  110. xGetSelfRunningInfo(runDir, NULL);
  111. if(runDir[strlen(runDir)-1] == '/') sprintf(lockFile, "%s%s", runDir, "status/");
  112. else sprintf(lockFile, "%s/%s", runDir, "status/");
  113. if(!sw_dir_exists(lockFile)) sw_dir_create(lockFile);
  114. strcat(lockFile, "dh_takephoto.lock");
  115. fd = open(lockFile, O_CREAT | O_RDWR | __O_CLOEXEC, 0666);
  116. if(-1 == fd) { return -1; }
  117. if(-1 == flock(fd, LOCK_EX | LOCK_NB)) { close(fd); return -2; }
  118. // 2, 查找相机, 获取其设备信息
  119. ret = IMV_EnumDevices(&devList, device_type);
  120. if(IMV_OK != ret)
  121. {
  122. sw_log_error("[%s] 枚举相机,执行错误, errCode=%d!!", MODULE_NAME, ret);
  123. goto end_p;
  124. }
  125. if(devList.nDevNum < 1)
  126. {
  127. ret = -3;
  128. sw_log_error("[%s] 没有相机,数量=%u!!", MODULE_NAME, devList.nDevNum);
  129. goto end_p;
  130. }
  131. findp:
  132. pDevInfo = &devList.pDevInfo[index++];
  133. if(!pDevInfo)
  134. {
  135. ret = -4;
  136. sw_log_error("[%s] unexpected internal error, failed to obtain camera information!!", MODULE_NAME);
  137. goto end_p;
  138. }
  139. if(pDevInfo->nCameraType == typeU3vCamera) ctx.camType = interfaceTypeUsb3;
  140. else if(pDevInfo->nCameraType == typeGigeCamera) ctx.camType = interfaceTypeGige;
  141. else { // SDK出错, 我只枚举了U口和G口相机, 不可能出现其他类型
  142. ret = -5;
  143. sw_log_error("[%s] unexpected internal error, unknown camera type(%u)!!", MODULE_NAME, pDevInfo->nCameraType);
  144. goto end_p;
  145. }
  146. strcpy(ctx.manuName, pDevInfo->vendorName);
  147. strcpy(imgMark.camModelName, pDevInfo->modelName);
  148. strcpy(imgMark.camSerialNum, pDevInfo->serialNumber);
  149. if(xstrcasecmp(ctx.manuName, manufacturer) != 0 && !is_huaray_manu_alias(ctx.manuName))
  150. {
  151. if(index < devList.nDevNum) goto findp;
  152. ret = -6;
  153. sw_log_error("[%s] 没有找到 \"%s\" 的相机!!", MODULE_NAME, manufacturer);
  154. goto end_p;
  155. }
  156. // 3, 打开相机, 设置其触发模式
  157. unsigned int cameraIndex = (index - 1);
  158. ret = IMV_CreateHandle(&hCam, modeByIndex, (void*)&cameraIndex);
  159. if(IMV_OK == ret) ret = IMV_Open(hCam);
  160. if(IMV_OK != ret)
  161. {
  162. sw_log_error("[%s] 打开相机时发生错误, errCode=%d!!", MODULE_NAME, ret);
  163. goto end_p;
  164. }
  165. if(IMV_OK == ret && xstrcasecmp(setTriggerMode, "On") == 0) ret = IMV_SetEnumFeatureSymbol(hCam, "TriggerSelector", "FrameStart"); // 设置触发条件
  166. if(IMV_OK == ret && xstrcasecmp(setTriggerMode, "On") == 0) ret = IMV_SetEnumFeatureSymbol(hCam, "TriggerSource", "Software"); // 设置软件触发
  167. if(IMV_OK == ret) ret = IMV_SetEnumFeatureSymbol(hCam, "TriggerMode", setTriggerMode); // 设置触发模式
  168. if(IMV_OK != ret)
  169. {
  170. sw_log_error("[%s] 设置相机时发生错误, errCode=%d!!", MODULE_NAME, ret);
  171. goto end_p;
  172. }
  173. // 4, 开始拍照, 等待完成后输出
  174. ret = IMV_GetEnumFeatureSymbol(hCam, "ExposureAuto", &exposureMode);
  175. if(IMV_OK != ret)
  176. {
  177. sw_log_error("[%s] 获取曝光模式时出错, errCode=%d!!", MODULE_NAME, ret);
  178. goto end_p;
  179. }
  180. if(xstrcasecmp(exposureMode.str, "Off") != 0) { ctx.isExposureAuto = true; clock_gettime(CLOCK_MONOTONIC, &ctx.expTime0); }
  181. else ctx.isExposureAuto = false;
  182. ctx.hCam = hCam;
  183. ctx.saveImgType = imgType;
  184. ctx.saveImgPath = saveImgPath;
  185. ctx.hESig = sw_signal_create();
  186. if(!ctx.hESig)
  187. {
  188. ret = -7;
  189. sw_log_error("[%s] SIG信号量创建失败!!", MODULE_NAME);
  190. goto end_p;
  191. }
  192. ctx.rCode = IMV_OK;
  193. ret = IMV_AttachGrabbing(hCam, OnFrameReceived, (void *)&ctx);
  194. if(IMV_OK != ret)
  195. {
  196. sw_log_error("[%s] 注册回调时发生错误, errCode=%d!!", MODULE_NAME, ret);
  197. goto end_p;
  198. }
  199. ret = IMV_StartGrabbing(hCam);
  200. if(IMV_OK != ret)
  201. {
  202. sw_log_error("[%s] 相机取流时发生错误, errCode=%d!!", MODULE_NAME, ret);
  203. goto end_p;
  204. }
  205. HANDLE hThrd = NULL;
  206. if(0 != xstrcasecmp(setTriggerMode, "On")) goto waitp;
  207. hThrd = sw_thrd_create("PhotoProc", THREAD_DEFAULT_PRIORITY, THREAD_DEFAULT_STACK_SIZE, FrameSoftTrigger, (unsigned long)&ctx, 0);
  208. if(!hThrd)
  209. {
  210. ret = -8;
  211. sw_signal_destroy(ctx.hESig);
  212. sw_log_error("[%s] 触发线程-创建失败!!", MODULE_NAME);
  213. goto end_p;
  214. }
  215. ctx.pUser = (void *)hThrd; sw_thrd_resume(hThrd);
  216. waitp:
  217. ret = sw_signal_wait(ctx.hESig, timeout*1000); // 阻塞等待拍照任务结束或超时
  218. if(0 == ret) ret = ctx.rCode;
  219. else
  220. {
  221. ret = -9;
  222. sw_log_error("[%s] 拍照过程-等待超时!!", MODULE_NAME);
  223. }
  224. if(ctx.pUser) sw_thrd_destroy(ctx.pUser, WAITTHRD_SAFEEXIT_TIMEOUT);
  225. sw_signal_destroy(ctx.hESig);
  226. // 5, 成功拍照, 输出相机的信息
  227. if(IMV_OK == ret)
  228. {
  229. double curExpTime = 0.0;
  230. IMV_GetDoubleFeatureValue(hCam, "ExposureTime", &curExpTime);
  231. imgMark.imgExposureTime = curExpTime;
  232. if(pImgMark) memcpy(pImgMark, &imgMark, sizeof(DHImgMark));
  233. }
  234. // 6, 任务结束, 释放相关的资源
  235. end_p:
  236. if(hCam)
  237. {
  238. IMV_StopGrabbing(hCam);
  239. IMV_Close(hCam);
  240. IMV_DestroyHandle(hCam);
  241. }
  242. flock(fd, LOCK_UN);
  243. close(fd);
  244. sw_file_delete(lockFile);
  245. return ret;
  246. }
  247. // 保存给定的数据帧到BMP图像文件, 成功返回: 0值, 失败返回: 非0值
  248. static int SaveFrameToBmp(IMV_HANDLE hCam, IMV_Frame *pFrame, const char *path)
  249. {
  250. IMV_PixelConvertParam stPixelConvertParam; int ret = IMV_OK;
  251. unsigned char *pDstBuf = NULL; unsigned int nDstBufSize = 0;
  252. nDstBufSize = sizeof(unsigned char) * pFrame->frameInfo.width * pFrame->frameInfo.height * 3;
  253. pDstBuf = (unsigned char*)malloc(nDstBufSize);
  254. if(!pDstBuf) return -18;
  255. memset(&stPixelConvertParam, 0, sizeof(stPixelConvertParam));
  256. stPixelConvertParam.nWidth = pFrame->frameInfo.width;
  257. stPixelConvertParam.nHeight = pFrame->frameInfo.height;
  258. stPixelConvertParam.ePixelFormat = pFrame->frameInfo.pixelFormat;
  259. stPixelConvertParam.pSrcData = pFrame->pData;
  260. stPixelConvertParam.nSrcDataLen = pFrame->frameInfo.size;
  261. stPixelConvertParam.nPaddingX = pFrame->frameInfo.paddingX;
  262. stPixelConvertParam.nPaddingY = pFrame->frameInfo.paddingY;
  263. stPixelConvertParam.eBayerDemosaic = demosaicNearestNeighbor;
  264. stPixelConvertParam.eDstPixelFormat = gvspPixelRGB8;
  265. stPixelConvertParam.pDstBuf = pDstBuf;
  266. stPixelConvertParam.nDstBufSize = nDstBufSize;
  267. ret = IMV_PixelConvert(hCam, &stPixelConvertParam);
  268. if(IMV_OK != ret) goto ret_p;
  269. ret = stbi_write_bmp(path, pFrame->frameInfo.width, pFrame->frameInfo.height, 3, pDstBuf);
  270. if(1 == ret) ret = IMV_OK;
  271. else ret = -19;
  272. ret_p:
  273. if(pDstBuf) free(pDstBuf);
  274. return ret;
  275. }
  276. // 保存给定的数据帧到JPG图像文件, 成功返回: 0值, 失败返回: 非0值
  277. static int SaveFrameToJpg(IMV_HANDLE hCam, IMV_Frame *pFrame, const char *path)
  278. {
  279. IMV_PixelConvertParam stPixelConvertParam; int ret = IMV_OK;
  280. unsigned char *pDstBuf = NULL; unsigned int nDstBufSize = 0;
  281. nDstBufSize = sizeof(unsigned char) * pFrame->frameInfo.width * pFrame->frameInfo.height * 3;
  282. pDstBuf = (unsigned char*)malloc(nDstBufSize);
  283. if(!pDstBuf) return -18;
  284. memset(&stPixelConvertParam, 0, sizeof(stPixelConvertParam));
  285. stPixelConvertParam.nWidth = pFrame->frameInfo.width;
  286. stPixelConvertParam.nHeight = pFrame->frameInfo.height;
  287. stPixelConvertParam.ePixelFormat = pFrame->frameInfo.pixelFormat;
  288. stPixelConvertParam.pSrcData = pFrame->pData;
  289. stPixelConvertParam.nSrcDataLen = pFrame->frameInfo.size;
  290. stPixelConvertParam.nPaddingX = pFrame->frameInfo.paddingX;
  291. stPixelConvertParam.nPaddingY = pFrame->frameInfo.paddingY;
  292. stPixelConvertParam.eBayerDemosaic = demosaicNearestNeighbor;
  293. stPixelConvertParam.eDstPixelFormat = gvspPixelRGB8;
  294. stPixelConvertParam.pDstBuf = pDstBuf;
  295. stPixelConvertParam.nDstBufSize = nDstBufSize;
  296. ret = IMV_PixelConvert(hCam, &stPixelConvertParam);
  297. if(IMV_OK != ret) goto ret_p;
  298. ret = stbi_write_jpg(path, pFrame->frameInfo.width, pFrame->frameInfo.height, 3, pDstBuf, 90);
  299. if(1 == ret) ret = IMV_OK;
  300. else ret = -19;
  301. ret_p:
  302. if(pDstBuf) free(pDstBuf);
  303. return ret;
  304. }
  305. // 保存给定的数据帧到指定格式("BMP"/"JPG")的图像文件,成功返回0值
  306. static int SavePhoto(HANDLE hCam, IMV_Frame *pFrame, DHImgType imgType, const char *imgPath)
  307. {
  308. char *path1 = (char *)imgPath, *path2 = NULL; int ret = IMV_OK; char ext[5];
  309. if(!hCam || !pFrame || !path1 || strlen(path1) <= 0) return -15;
  310. switch(imgType)
  311. {
  312. case IMG_TYPE_BMP: // 保存为.bmp格式
  313. strcpy(ext, ".bmp");
  314. break;
  315. case IMG_TYPE_JPG: // 保存为.jpg格式
  316. strcpy(ext, ".jpg");
  317. break;
  318. default: return -16;
  319. }
  320. if(!xstrcasestr(path1, "right", ext))
  321. {
  322. path2 = (char *)sw_heap_malloc(strlen(path1) + sizeof(ext));
  323. if(path2) sprintf(path2, "%s%s", path1, ext);
  324. else return -17;
  325. if(imgType == IMG_TYPE_BMP) ret = SaveFrameToBmp(hCam, pFrame, path2);
  326. if(imgType == IMG_TYPE_JPG) ret = SaveFrameToJpg(hCam, pFrame, path2);
  327. sw_heap_free(path2);
  328. }
  329. else
  330. {
  331. if(imgType == IMG_TYPE_BMP) ret = SaveFrameToBmp(hCam, pFrame, path1);
  332. if(imgType == IMG_TYPE_JPG) ret = SaveFrameToJpg(hCam, pFrame, path1);
  333. }
  334. return ret;
  335. }
  336. // 获取系统当前USBFS内存大小(MB), 成功返回: >=0, 失败返回: <0值
  337. int DH_GetSysUsbfsMemCurrentSize()
  338. {
  339. const char *usbfsFile = "/sys/module/usbcore/parameters/usbfs_memory_mb";
  340. char buf[MAX_LINE_CHARS]; int val = -1;
  341. FILE *fp = fopen(usbfsFile, "r");
  342. if(fp)
  343. {
  344. if(fgets(buf, sizeof(buf), fp)) sscanf(buf, "%d", &val);
  345. fclose(fp);
  346. }
  347. return val;
  348. }
  349. // 设置系统新的USBFS内存大小(MB), 成功返回: 0值, 失败返回: <0值
  350. int DH_SetSysUsbfsMemSize(int val)
  351. {
  352. const char *usbfsFile = "/sys/module/usbcore/parameters/usbfs_memory_mb";
  353. char buf[MAX_LINE_CHARS]; sprintf(buf, "%d\n", val);
  354. if(val >= 0 && sw_file_update(usbfsFile, "w", buf, strlen(buf)) == strlen(buf)) return 0;
  355. else return -1;
  356. }
  357. // 获取当前已连接的海康相机的数量, 成功返回: >=0, 失败返回: <0值
  358. int DH_GetCameraCount()
  359. {
  360. IMV_DeviceList devList = { 0 };
  361. int ret;
  362. ret = IMV_EnumDevices(&devList, device_type);
  363. if(IMV_OK != ret)
  364. {
  365. sw_log_error("[%s] 枚举相机,执行错误, errCode=%d!!", MODULE_NAME, ret);
  366. return -1;
  367. }
  368. ret = devList.nDevNum;
  369. if(ret > 0)
  370. {
  371. int index = 0, cnt = 0; IMV_DeviceInfo *pDevInfo; char name[MAX_LINE_CHARS];
  372. findp:
  373. pDevInfo = &devList.pDevInfo[index++];
  374. name[0] = '\0';
  375. if(pDevInfo && (pDevInfo->nCameraType == typeU3vCamera || pDevInfo->nCameraType == typeGigeCamera)) strcpy(name, pDevInfo->vendorName);
  376. if(xstrcasecmp(name, manufacturer) == 0 || is_huaray_manu_alias(name)) cnt++;
  377. if(index < devList.nDevNum) goto findp;
  378. else ret = cnt;
  379. }
  380. return ret;
  381. }