#include "takephoto.h" // 模块名称 static const char MODULE_NAME[] = "HrTakePhoto"; // 相机类型 static unsigned int device_type = interfaceTypeGige | interfaceTypeUsb3; // 制造厂商 static const char * const manufacturer = "Huaray Technology"; // 触发模式 static const char * const setTriggerMode = "On"; /// 默认: 打开, 可关闭: "Off" // 曝光取图 #define AE_WAIT_MAX_MS 180000 // 最大曝光稳定等待的时间, 默认3分钟, 单位: ms #define AE_EXP_TIME_EPS_US 50 // 曝光时间稳定判定的误差, 防抖&区间, 单位: us #define AE_EXP_STABLE_FRAMES 4 // 相机处于自动曝光模式时, 累计曝光值稳定的帧数 // 保存照片 static int SavePhoto(HANDLE hCam, IMV_Frame *pFrame, DHImgType imgType, const char *imgFile); // 拍照回调 typedef struct { HANDLE hCam; // 打开相机的句柄 uint32_t camType; // 相机类型:U、网 char manuName[MAX_LINE_CHARS]; //制造厂商名称 DHImgType saveImgType; // 保存图像的类型 const char *saveImgPath; // 保存图像的路径 bool isExposureAuto; // 自动曝光:是/否 struct timespec expTime0; // 曝光开始的时间 float lastExpTime; // 上次的曝光时长(us) int expStableCnt; // 连续曝光稳定帧计数 HANDLE hESig; // 任务结束的通知 int rCode; // 任务结束返回值 } PthotoProcCtx; // 非标准的"Huaray Technology"制造商名称, 但也是华睿的相机, 如: "Machine Vision" static const char * const huaray_manu_aliases[] = { "Machine Vision", NULL }; static bool is_huaray_manu_alias(const char *name) { for(int i = 0; huaray_manu_aliases[i] != NULL; i++) { if(xstrcasecmp(name, huaray_manu_aliases[i]) == 0) return true; } return false; } static void OnFrameReceived(IMV_Frame *pFrame, void *pUser) // 数据帧的回调, 完成一次拍照任务 { PthotoProcCtx *ctx = (PthotoProcCtx *)pUser; int ret = IMV_OK; if(NULL == pFrame) return; // 1, 打印该帧信息 sw_log_debug("[%s] +++GetOneFrame+++, Width[%u], Height[%u], FrameNum[%llu], FrameLen[%u], PixelType[0x%08X]", MODULE_NAME, pFrame->frameInfo.width, pFrame->frameInfo.height, pFrame->frameInfo.blockId, pFrame->frameInfo.size, (unsigned int)pFrame->frameInfo.pixelFormat); // 2, 自动曝光模式 if(ctx->isExposureAuto) { double curExpTime = 0.0; ret = IMV_GetDoubleFeatureValue(ctx->hCam, "ExposureTime", &curExpTime); if(IMV_OK != ret) goto end_p; struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); long elapsed = (now.tv_sec - ctx->expTime0.tv_sec)*1000 + (now.tv_nsec-ctx->expTime0.tv_nsec)/(1000*1000); // 计算时间差, 单位: ms sw_log_debug("[%s] +++GetOneFrame+++, 等待曝光完成, ExposureTime = %.2fus, ElapsedTime = %ldms", MODULE_NAME, curExpTime, elapsed); if(fabsf(curExpTime - ctx->lastExpTime) <= AE_EXP_TIME_EPS_US) ctx->expStableCnt++; else ctx->expStableCnt = 0; if(ctx->expStableCnt < AE_EXP_STABLE_FRAMES && elapsed < AE_WAIT_MAX_MS) { // 曝光未稳定, 也未超时, 则继续等下一帧 ctx->lastExpTime = curExpTime; if(0 == ctx->expStableCnt) ctx->expStableCnt = 1; return; } } // 3, 导出图像文件 ret = SavePhoto(ctx->hCam, pFrame, ctx->saveImgType, ctx->saveImgPath); // 4, 设置拍照完成 end_p: ctx->rCode = ret; sw_signal_give(ctx->hESig); } static int FrameSoftTrigger(unsigned long wParam, unsigned long lParam) // 线程回调函数, 触发一次拍照执行 { PthotoProcCtx *ctx = (PthotoProcCtx *)wParam; int ret = IMV_ExecuteCommandFeature(ctx->hCam, "TriggerSoftware"); if(IMV_OK != ret) ret = 1; // 软触发失败后, 马上再次触发 else ret = 1000; // 软触发成功后-延时一秒, 等待下次的触发 return ret; } // 单次执行相机拍照, 并保存到文件, 成功返回: 0值, 失败返回:非0值 // "imgType" - 获取图像类型 // "saveImgPath" - 保存的文件名 // "timeout" - 等待超时时间, 单位:秒: < 0 表示无超时; // 无论超时怎么设置, 首次尝试拍照一定会执行 // , 但时间不确定; 拍照成功、超时或发生错误 // 时会自动结束任务(相机持续无数据的时间). // "pImgMark" - 输出本次拍照图像的水印信息, 可以设置NULL int DH_TakePhoto(DHImgType imgType, const char *saveImgPath, int timeout, DHImgMark *pImgMark) { int fd; char runDir[MAX_PATH_CHARS] = { 0 }, lockFile[MAX_PATH_CHARS+32] = { 0 }; int ret, index = 0; DHImgMark imgMark = { 0 }; HANDLE hCam = NULL; PthotoProcCtx ctx = { 0 }; IMV_DeviceList devList = { 0 }; IMV_DeviceInfo *pDevInfo = NULL; IMV_String exposureMode = { 0 }; // 1, 占用锁定, 避免同时间拍照 xGetSelfRunningInfo(runDir, NULL); if(runDir[strlen(runDir)-1] == '/') sprintf(lockFile, "%s%s", runDir, "status/"); else sprintf(lockFile, "%s/%s", runDir, "status/"); if(!sw_dir_exists(lockFile)) sw_dir_create(lockFile); strcat(lockFile, "dh_takephoto.lock"); fd = open(lockFile, O_CREAT | O_RDWR | __O_CLOEXEC, 0666); if(-1 == fd) { return -1; } if(-1 == flock(fd, LOCK_EX | LOCK_NB)) { close(fd); return -2; } // 2, 查找相机, 获取其设备信息 ret = IMV_EnumDevices(&devList, device_type); if(IMV_OK != ret) { sw_log_error("[%s] 枚举相机,执行错误, errCode=%d!!", MODULE_NAME, ret); goto end_p; } if(devList.nDevNum < 1) { ret = -3; sw_log_error("[%s] 没有相机,数量=%u!!", MODULE_NAME, devList.nDevNum); goto end_p; } findp: pDevInfo = &devList.pDevInfo[index++]; if(!pDevInfo) { ret = -4; sw_log_error("[%s] unexpected internal error, failed to obtain camera information!!", MODULE_NAME); goto end_p; } if(pDevInfo->nCameraType == typeU3vCamera) ctx.camType = interfaceTypeUsb3; else if(pDevInfo->nCameraType == typeGigeCamera) ctx.camType = interfaceTypeGige; else { // SDK出错, 我只枚举了U口和G口相机, 不可能出现其他类型 ret = -5; sw_log_error("[%s] unexpected internal error, unknown camera type(%u)!!", MODULE_NAME, pDevInfo->nCameraType); goto end_p; } strcpy(ctx.manuName, pDevInfo->vendorName); strcpy(imgMark.camModelName, pDevInfo->modelName); strcpy(imgMark.camSerialNum, pDevInfo->serialNumber); if(xstrcasecmp(ctx.manuName, manufacturer) != 0 && !is_huaray_manu_alias(ctx.manuName)) { if(index < devList.nDevNum) goto findp; ret = -6; sw_log_error("[%s] 没有找到 \"%s\" 的相机!!", MODULE_NAME, manufacturer); goto end_p; } // 3, 打开相机, 设置其触发模式 unsigned int cameraIndex = (index - 1); ret = IMV_CreateHandle(&hCam, modeByIndex, (void*)&cameraIndex); if(IMV_OK == ret) ret = IMV_Open(hCam); if(IMV_OK != ret) { sw_log_error("[%s] 打开相机时发生错误, errCode=%d!!", MODULE_NAME, ret); goto end_p; } if(IMV_OK == ret && xstrcasecmp(setTriggerMode, "On") == 0) ret = IMV_SetEnumFeatureSymbol(hCam, "TriggerSelector", "FrameStart"); // 设置触发条件 if(IMV_OK == ret && xstrcasecmp(setTriggerMode, "On") == 0) ret = IMV_SetEnumFeatureSymbol(hCam, "TriggerSource", "Software"); // 设置软件触发 if(IMV_OK == ret) ret = IMV_SetEnumFeatureSymbol(hCam, "TriggerMode", setTriggerMode); // 设置触发模式 if(IMV_OK != ret) { sw_log_error("[%s] 设置相机时发生错误, errCode=%d!!", MODULE_NAME, ret); goto end_p; } // 4, 开始拍照, 等待完成后输出 ret = IMV_GetEnumFeatureSymbol(hCam, "ExposureAuto", &exposureMode); if(IMV_OK != ret) { sw_log_error("[%s] 获取曝光模式时出错, errCode=%d!!", MODULE_NAME, ret); goto end_p; } if(xstrcasecmp(exposureMode.str, "Off") != 0) { ctx.isExposureAuto = true; clock_gettime(CLOCK_MONOTONIC, &ctx.expTime0); } else ctx.isExposureAuto = false; ctx.hCam = hCam; ctx.saveImgType = imgType; ctx.saveImgPath = saveImgPath; ctx.hESig = sw_signal_create(); if(!ctx.hESig) { ret = -7; sw_log_error("[%s] SIG信号量创建失败!!", MODULE_NAME); goto end_p; } ctx.rCode = IMV_OK; ret = IMV_AttachGrabbing(hCam, OnFrameReceived, (void *)&ctx); if(IMV_OK != ret) { sw_log_error("[%s] 注册回调时发生错误, errCode=%d!!", MODULE_NAME, ret); goto end_p; } ret = IMV_StartGrabbing(hCam); if(IMV_OK != ret) { sw_log_error("[%s] 相机取流时发生错误, errCode=%d!!", MODULE_NAME, ret); goto end_p; } HANDLE hThrd = NULL; if(0 != xstrcasecmp(setTriggerMode, "On")) goto waitp; hThrd = sw_thrd_create("PhotoProc", THREAD_DEFAULT_PRIORITY, THREAD_DEFAULT_STACK_SIZE, FrameSoftTrigger, (unsigned long)&ctx, 0); if(!hThrd) { ret = -8; sw_signal_destroy(ctx.hESig); sw_log_error("[%s] 触发线程-创建失败!!", MODULE_NAME); goto end_p; } sw_thrd_resume(hThrd); waitp: ret = sw_signal_wait(ctx.hESig, timeout*1000); // 阻塞等待拍照任务结束或超时 if(0 == ret) ret = ctx.rCode; else { ret = -9; sw_log_error("[%s] 拍照过程-等待超时!!", MODULE_NAME); } if(hThrd) sw_thrd_destroy(hThrd, WAITTHRD_SAFEEXIT_TIMEOUT); sw_signal_destroy(ctx.hESig); // 5, 成功拍照, 输出相机的信息 if(IMV_OK == ret) { double curExpTime = 0.0; IMV_GetDoubleFeatureValue(hCam, "ExposureTime", &curExpTime); imgMark.imgExposureTime = curExpTime; if(pImgMark) memcpy(pImgMark, &imgMark, sizeof(DHImgMark)); } // 6, 任务结束, 释放相关的资源 end_p: if(hCam) { IMV_StopGrabbing(hCam); IMV_Close(hCam); IMV_DestroyHandle(hCam); } flock(fd, LOCK_UN); close(fd); sw_file_delete(lockFile); return ret; } // 保存照片 static int SavePhoto(HANDLE hCam, IMV_Frame *pFrame, DHImgType imgType, const char *imgFile) { return IMV_OK; // todo: 实现保存照片功能 } // 获取系统当前USBFS内存大小(MB), 成功返回: >=0, 失败返回: <0值 int DH_GetSysUsbfsMemCurrentSize() { const char *usbfsFile = "/sys/module/usbcore/parameters/usbfs_memory_mb"; char buf[MAX_LINE_CHARS]; int val = -1; FILE *fp = fopen(usbfsFile, "r"); if(fp) { if(fgets(buf, sizeof(buf), fp)) sscanf(buf, "%d", &val); fclose(fp); } return val; } // 设置系统新的USBFS内存大小(MB), 成功返回: 0值, 失败返回: <0值 int DH_SetSysUsbfsMemSize(int val) { const char *usbfsFile = "/sys/module/usbcore/parameters/usbfs_memory_mb"; char buf[MAX_LINE_CHARS]; sprintf(buf, "%d\n", val); if(val >= 0 && sw_file_update(usbfsFile, "w", buf, strlen(buf)) == strlen(buf)) return 0; else return -1; } // 获取当前已连接的海康相机的数量, 成功返回: >=0, 失败返回: <0值 int DH_GetCameraCount() { IMV_DeviceList devList = { 0 }; int ret; ret = IMV_EnumDevices(&devList, device_type); if(IMV_OK != ret) { sw_log_error("[%s] 枚举相机,执行错误, errCode=%d!!", MODULE_NAME, ret); return -1; } ret = devList.nDevNum; if(ret > 0) { int index = 0, cnt = 0; IMV_DeviceInfo *pDevInfo; char name[MAX_LINE_CHARS]; findp: pDevInfo = &devList.pDevInfo[index++]; name[0] = '\0'; if(pDevInfo && (pDevInfo->nCameraType == typeU3vCamera || pDevInfo->nCameraType == typeGigeCamera)) strcpy(name, pDevInfo->vendorName); if(xstrcasecmp(name, manufacturer) == 0 || is_huaray_manu_alias(name)) cnt++; if(index < devList.nDevNum) goto findp; else ret = cnt; } return ret; }