Android Jni 利用transpose()和flip()函数实现图像旋转90°

一,上java代码

private Button btnProc;
private ImageView imageView;
private Bitmap bmp;

// Used to load the 'native-lib' library on application startup.
static {
    System.loadLibrary("native-lib");
}

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    // Example of a call to a native method
    btnProc = (Button) findViewById(R.id.btn_gray_process);
    imageView = (ImageView) findViewById(R.id.image_view);

    bmp = BitmapFactory.decodeResource(getResources(), R.drawable.test7);
    imageView.setImageBitmap(bmp);
    btnProc.setOnClickListener(this);
}

/**
 * A native method that is implemented by the 'native-lib' native library,
 * which is packaged with this application.
 */
public static native int[] grayProc(int[] pixels, int w, int h);


@Override
public void onClick(View view) {

    int w = bmp.getWidth();
    int h = bmp.getHeight();
    int[] pixels = new int[w*h];
    bmp.getPixels(pixels, 0, w, 0, 0, w, h);

    long startTime = System.currentTimeMillis();
    int[] resultInt = grayProc(pixels, w, h);
    long endTime = System.currentTimeMillis();

    Log.e("JNITime",""+(endTime-startTime));
    Bitmap resultImg = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);

    //(@ColorInt int[] pixels, int offset, int stride,int x, int y, int width, int height)
    resultImg.setPixels(resultInt, 0, w, 0, 0, w, h);
    imageView.setImageBitmap(resultImg);

}

二,实现jni方法

extern “C”
JNIEXPORT jintArray JNICALL
Java_com_example_dgxq008_opencv_1readpixel_MainActivity_grayProc(JNIEnv *env, jclass type
, jintArray pixels_
, jint w
, jint h) {

jint* pixels = env->GetIntArrayElements(pixels_, NULL);
if (pixels==NULL){
    return 0;
}

//图片一进来时是ARGB  通过mat转换BGRA
Mat img(h,w,CV_8UC4,(uchar *)pixels);  //pixels 操作的是同一份数据
Mat temp;

transpose(img,temp);
//Size_(_Tp _width, _Tp _height);
resize(temp,img,Size(w,h));

/**
  * 翻转图片
  * 第三个参数,0以x轴进行翻转,1以y轴翻转,-1以xy同时翻转
  */
flip(img,img,1);

//对应数据指针
uchar* ptr = img.data;

int size = w*h;
jintArray result = env->NewIntArray(size);
env->SetIntArrayRegion(result,0,size,pixels);

env->ReleaseIntArrayElements(pixels_, pixels, 0);

return result;

}

import org.opencv.core.*; import org.opencv.imgproc.Imgproc; import org.opencv.videoio.VideoWriter; import org.opencv.videoio.Videoio; import java.io.File; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicBoolean; import static org.opencv.imgcodecs.Imgcodecs.imwrite; import static org.opencv.imgproc.Imgproc.putText; // 引入海康 SDK 类(假设已正确引入 MvImport.jar) import MvCameraControlWrapper.*; import static MvCameraControlWrapper.MvCameraControlDefines.*; public class IndustrialCameraDemo { // ==================== 配置参数 ==================== private static final String DESKTOP_TEST_DIR = "C:\\Users\\guolin12\\Desktop\\test"; private static final double RECORD_DURATION_SECONDS = 10.0; private static final int MAX_QUEUE_SIZE = 30; // ======================================== // 全局状态变量 // ======================================== private static final AtomicBoolean g_bExit = new AtomicBoolean(false); private static final AtomicBoolean g_bIsRecording = new AtomicBoolean(false); private static final AtomicBoolean g_bEnableRotation = new AtomicBoolean(true); private static final BlockingQueue<ImageFrame> g_frameQueue = new ArrayBlockingQueue<>(MAX_QUEUE_SIZE); private static final Object queueMonitor = new Object(); private static int g_width = 0, g_height = 0; private static float g_fps = 25.0f; private static MV_CC_DEVICE_INFO_LIST deviceList; private static MvCamera camera; static { // 加载 OpenCV native 库 System.loadLibrary(Core.NATIVE_LIBRARY_NAME); } // ======================================== // 自定义帧结构体 // ======================================== public static class ImageFrame { public final byte[] data; public final int width; public final int height; public final int pixelFormat; public final long timestamp; // ns public ImageFrame(byte[] data, int width, int height, int pixelFormat) { this.data = data.clone(); this.width = width; this.height = height; this.pixelFormat = pixelFormat; this.timestamp = System.nanoTime(); } } // ======================================== // 获取输出目录(优先桌面,否则 Temp) // ======================================== private static File getOutputDirectory() { File desktopDir = new File(DESKTOP_TEST_DIR); if (desktopDir.exists() || desktopDir.mkdirs()) { try { new File(desktopDir, ".test").createNewFile(); new File(desktopDir, ".test").delete(); System.out.println("✅ Using output directory: " + desktopDir); return desktopDir; } catch (Exception ignored) {} } File tempDir = new File(System.getProperty("java.io.tmpdir")); System.out.println("❌ Cannot write to desktop, falling back to: " + tempDir); return tempDir; } // ======================================== // 设置首选像素格式 // ======================================== private static boolean setPreferredPixelFormat(MvCamera cam) { int[] formats = { MV_CC.MV_GIGE_PIX_FORMAT_RGB8, MV_CC.MV_GIGE_PIX_FORMAT_BAYERRG8, MV_CC.MV_GIGE_PIX_FORMAT_MONO8 }; String[] names = {"RGB8", "BayerRG8", "Mono8"}; for (int i = 0; i < formats.length; i++) { int ret = cam.MV_CC_SetEnumValue("PixelFormat", formats[i]); if (ret == MV_CC.MV_OK) { System.out.println("✅ Set PixelFormat to " + names[i]); return true; } } System.err.println("❌ No supported pixel format available!"); return false; } // ======================================== // 对齐宽高(某些相机要求对齐) // ======================================== private static void alignImageSize(MvCamera cam) { IntByReference widthRef = new IntByReference(); cam.MV_CC_GetIntValue("Width", widthRef); int alignedWidth = (widthRef.getValue() / 32) * 32; cam.MV_CC_SetIntValue("Width", alignedWidth); g_width = alignedWidth; System.out.println("🔧 Set Width to " + alignedWidth); IntByReference heightRef = new IntByReference(); cam.MV_CC_GetIntValue("Height", heightRef); int alignedHeight = (heightRef.getValue() / 2) * 2; cam.MV_CC_SetIntValue("Height", alignedHeight); g_height = alignedHeight; System.out.println("🔧 Set Height to " + alignedHeight); } // ======================================== // 转换为 BGR Mat(用于显示/保存) // ======================================== private static Mat convertToBgr(byte[] data, int width, int height, int pixelFormat) { Mat mat; switch (pixelFormat) { case MV_CC.MV_GIGE_PIX_FORMAT_MONO8: mat = new Mat(height, width, CvType.CV_8UC1); mat.put(0, 0, data); Mat bgr = new Mat(); Imgproc.cvtColor(mat, bgr, Imgproc.COLOR_GRAY2BGR); mat.release(); return bgr; case MV_CC.MV_GIGE_PIX_FORMAT_BAYERRG8: mat = new Mat(height, width, CvType.CV_8UC1); mat.put(0, 0, data); Mat bgr2 = new Mat(); Imgproc.cvtColor(mat, bgr2, Imgproc.COLOR_BayerBG2BGR); // 注意模式可能不同 mat.release(); return bgr2; case MV_CC.MV_GIGE_PIX_FORMAT_RGB8: mat = new Mat(height, width, CvType.CV_8UC3); mat.put(0, 0, data); Mat bgr3 = new Mat(); Imgproc.cvtColor(mat, bgr3, Imgproc.COLOR_RGB2BGR); mat.release(); return bgr3; default: System.err.println("⚠️ Unsupported pixel format: " + pixelFormat); return new Mat(height, width, CvType.CV_8UC3, new Scalar(0, 0, 0)); } } // ======================================== // 图像旋转(使用 OpenCV 实现,替代 MV_CC_RotateImage) // ======================================== private static Mat rotateImage(Mat src) { Mat rotated = new Mat(); Core.flip(src.t(), rotated, 1); // transpose + flip = 90° clockwise return rotated; } // ======================================== // 🎥 录像线程任务 // ======================================== private static void startRecording(String filePath, int width, int height, int targetFrameCount, float fps) { System.out.printf("🎥 Recording started: %s%n", filePath); System.out.printf("🎯 Target frames: %d, Output FPS: %.3f%n", targetFrameCount, fps); VideoWriter writer = new VideoWriter(); int fourcc = VideoWriter.fourcc('M', 'J', 'P', 'G'); boolean opened = writer.open(filePath, fourcc, fps, new Size(width, height), true); if (!opened) { System.err.println("❌ Failed to open VideoWriter: " + filePath); g_bIsRecording.set(false); return; } int recorded = 0; long startTime = System.currentTimeMillis(); try { while (g_bIsRecording.get() && !g_bExit.get() && recorded < targetFrameCount) { ImageFrame frame = g_frameQueue.poll(3, TimeUnit.SECONDS); if (frame == null) { System.out.println("📹 Timeout waiting for frame."); break; } Mat bgr = convertToBgr(frame.data, frame.width, frame.height, frame.pixelFormat); if (bgr.empty()) continue; writer.write(bgr); bgr.release(); recorded++; } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } catch (Exception e) { System.err.println("❌ Exception during recording: " + e.getMessage()); } finally { writer.release(); long duration = System.currentTimeMillis() - startTime; System.out.printf("⏹️ Saved video: %s%n", filePath); System.out.printf("📊 Wrote %d frames at %.3f FPS%n", recorded, fps); System.out.printf("⏱️ Actual time: %.3f sec%n", duration / 1000.0); g_bIsRecording.set(false); } } // ======================================== // 🔧 主工作线程:采集 + 显示 + 控制 // ======================================== private static void workThread() { HighGui.namedWindow("Live View"); alignImageSize(camera); // 获取帧率 FloatByReference fpsRef = new FloatByReference(); if (camera.MV_CC_GetFloatValue("ResultingFrameRate", fpsRef) == MV_CC.MV_OK) { g_fps = fpsRef.getValue(); System.out.printf("🎯 Actual Camera FPS: %.3f%n", g_fps); } else { System.out.println("⚠️ Could not get FPS, using default: " + g_fps); } File outputDir = getOutputDirectory(); FrameGrabber grabber = new FrameGrabber(); grabber.start(); while (!g_bExit.get()) { try { MV_FRAME_OUT frameOut = grabber.getFrame(1000); if (frameOut == null) { System.out.println("⚠️ Timeout getting frame."); continue; } MV_FRAME_OUT_INFO_EX info = frameOut.stFrameInfo; byte[] srcData = frameOut.pBufAddr; int outWidth = info.nWidth; int outHeight = info.nHeight; byte[] imgData; boolean shouldRotate = g_bEnableRotation.get(); if (shouldRotate) { Mat srcMat = convertToBgr(srcData, info.nWidth, info.nHeight, info.enPixelType); Mat rotMat = rotateImage(srcMat); imgData = matToByteArray(rotMat); outWidth = rotMat.cols(); outHeight = rotMat.rows(); srcMat.release(); rotMat.release(); } else { imgData = srcData.clone(); } Mat displayMat = convertToBgr(imgData, outWidth, outHeight, info.enPixelType); if (!displayMat.empty()) { String status = g_bIsRecording.get() ? "🔴 RECORDING..." : "✅ LIVE STREAM"; String rotation = g_bEnableRotation.get() ? "🔄 ROTATION: ON" : "➡️ ROTATION: OFF"; putText(displayMat, status, new Point(20, 40), Imgproc.FONT_HERSHEY_SIMPLEX, 1.0, new Scalar(0, 255, 0), 2); putText(displayMat, rotation, new Point(20, 80), Imgproc.FONT_HERSHEY_SIMPLEX, 0.8, new Scalar(255, 255, 0), 2); HighGui.imshow("Live View", displayMat); displayMat.release(); } int key = HighGui.waitKey(1); if (key == 27 || key == 'q' || key == 'Q') { g_bExit.set(true); } else if ((key == 'r' || key == 'R') && !g_bIsRecording.get()) { if (outputDir.exists()) { g_bIsRecording.set(true); int targetFrameCount = (int)(RECORD_DURATION_SECONDS * g_fps); LocalDateTime now = LocalDateTime.now(); DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss_SSS"); String fileName = String.format("rec_%s.avi", now.format(fmt)); String filePath = new File(outputDir, fileName).getAbsolutePath(); new Thread(() -> startRecording(filePath, outWidth, outHeight, targetFrameCount, g_fps)).start(); } else { System.err.println("❌ Output directory not writable!"); g_bIsRecording.set(false); } } else if (key == 't' || key == 'T') { boolean newState = !g_bEnableRotation.getAndSet(!g_bEnableRotation.get()); System.out.println("🔄 Rotation toggled: " + (newState ? "ON" : "OFF")); } if (g_bIsRecording.get()) { synchronized (queueMonitor) { while (g_frameQueue.size() >= MAX_QUEUE_SIZE) { g_frameQueue.poll(); } g_frameQueue.offer(new ImageFrame(imgData, outWidth, outHeight, info.enPixelType)); } } camera.MV_CC_FreeImageBuffer(frameOut); } catch (Exception e) { System.err.println("❌ Frame error: " + e.getMessage()); } } grabber.interrupt(); try { grabber.join(); } catch (InterruptedException ignored) {} HighGui.destroyAllWindows(); } // 辅助:Mat 转 byte[] private static byte[] matToByteArray(Mat mat) { MatOfByte mob = new MatOfByte(); imwrite(".temp.jpg", mat, mob); return mob.toArray(); } // 内部类:异步抓帧 private static class FrameGrabber extends Thread { private volatile boolean running = true; @Override public void run() { while (running && !g_bExit.get()) { try { Thread.sleep(10); } catch (InterruptedException e) { running = false; Thread.currentThread().interrupt(); } } } public MV_FRAME_OUT getFrame(int timeoutMs) { MV_FRAME_OUT frame = new MV_FRAME_OUT(); int ret = camera.MV_CC_GetImageBuffer(frame, timeoutMs); return ret == MV_CC.MV_OK ? frame : null; } } // ======================================== // 🚀 主函数 // ======================================== public static void main(String[] args) { int nRet; // 初始化 SDK nRet = MV_CC.MV_CC_Initialize(); if (nRet != MV_CC.MV_OK) { System.err.println("❌ Initialize SDK failed! [" + Integer.toHexString(nRet) + "]"); return; } // 枚举设备 deviceList = new MV_CC_DEVICE_INFO_LIST(); nRet = MV_CC.MV_CC_EnumDevices(MV_CC.MV_GIGE_DEVICE | MV_CC.MV_USB_DEVICE, deviceList); if (nRet != MV_CC.MV_OK || deviceList.nDeviceNum == 0) { System.err.println("❌ No devices found! [" + Integer.toHexString(nRet) + "]"); MV_CC.MV_CC_Finalize(); return; } System.out.println("✅ Found " + deviceList.nDeviceNum + " device(s):"); for (int i = 0; i < deviceList.nDeviceNum; i++) { MV_CC_DEVICE_INFO info = deviceList.pDeviceInfo[i]; if (info.nTLayerType == MV_CC.MV_GIGE_DEVICE) { int ip = info.SpecialInfo.stGigEInfo.nCurrentIp; String ipStr = String.format("%d.%d.%d.%d", (ip >> 24) & 0xFF, (ip >> 16) & 0xFF, (ip >> 8) & 0xFF, ip & 0xFF); System.out.println(" [" + i + "] IP: " + ipStr + ", Model: " + info.SpecialInfo.stGigEInfo.chModelName); } else if (info.nTLayerType == MV_CC.MV_USB_DEVICE) { System.out.println(" [" + i + "] Model: " + info.SpecialInfo.stUsb3VInfo.chModelName + ", Serial: " + info.SpecialInfo.stUsb3VInfo.chSerialNumber); } } System.out.print("👉 Input camera index (0-" + (deviceList.nDeviceNum - 1) + "): "); java.util.Scanner scanner = new java.util.Scanner(System.in); int index = scanner.nextInt(); if (index >= deviceList.nDeviceNum) { System.err.println("❌ Invalid index!"); return; } // 创建句柄 camera = new MvCamera(); nRet = camera.MV_CC_CreateHandle(deviceList.pDeviceInfo[index]); if (nRet != MV_CC.MV_OK) { System.err.println("❌ CreateHandle failed! [" + Integer.toHexString(nRet) + "]"); return; } // 打开设备 nRet = camera.MV_CC_OpenDevice(); if (nRet != MV_CC.MV_OK) { System.err.println("❌ OpenDevice failed! [" + Integer.toHexString(nRet) + "]"); cleanup(); return; } // GigE 优化 if (deviceList.pDeviceInfo[index].nTLayerType == MV_CC.MV_GIGE_DEVICE) { int packetSize = camera.MV_CC_GetOptimalPacketSize(); if (packetSize > 0) { camera.MV_CC_SetIntValueEx("GevSCPSPacketSize", packetSize); } } // 关闭触发 camera.MV_CC_SetEnumValue("TriggerMode", 0); // 设置像素格式 if (!setPreferredPixelFormat(camera)) { cleanup(); return; } // 开始采集 nRet = camera.MV_CC_StartGrabbing(); if (nRet != MV_CC.MV_OK) { System.err.println("❌ StartGrabbing failed! [" + Integer.toHexString(nRet) + "]"); cleanup(); return; } System.out.println("\n📌 Instructions:"); System.out.printf(" ➡ Press 'R' to record %.1f seconds video%n", RECORD_DURATION_SECONDS); System.out.printf(" ➡ Press 'T' to toggle rotation (current: %s)%n", g_bEnableRotation.get() ? "ON" : "OFF"); System.out.println(" ➡ Live preview will NOT freeze during recording!"); System.out.println(" ➡ Press 'Q' or ESC to exit\n"); // 启动主工作线程 new Thread(IndustrialCameraDemo::workThread).start(); // 主循环等待退出 while (!g_bExit.get()) { try { Thread.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } } cleanup(); System.out.println("👋 Program exited."); } private static void cleanup() { g_bExit.set(true); g_bIsRecording.set(false); if (camera != null) { camera.MV_CC_StopGrabbing(); camera.MV_CC_CloseDevice(); camera.MV_CC_DestroyHandle(); } MV_CC.MV_CC_Finalize(); } } 不适用Opencv接口,使用海康自身的旋转接口进行旋转
最新发布
11-22
//本demo演示了如何使用工业相机进行前端播放、后端录制以及图像旋转功能,R / r 开始录像(10 秒);T / t 切换是否启用图像旋转;Q / q / ESC 退出程序 #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <Windows.h> #include <process.h> #include <conio.h> #include <thread> #include <atomic> #include <mutex> #include <string> #include <chrono> #include <vector> #include <queue> #include <condition_variable> // OpenCV 头文件 #include <opencv2/opencv.hpp> // 海康威视 MVS SDK 头文件 #include "MvCameraControl.h" #pragma comment(lib, "MvCameraControl.lib") // 链接海康 SDK 库 #pragma comment(lib, "opencv_world480.lib") // 请根据实际 OpenCV 版本修改此行 // ==================== 配置参数 ==================== const char* const DESKTOP_TEST_DIR = "C:\\Users\\guolin12\\Desktop\\test"; const double RECORD_DURATION_SECONDS = 10.0; // 每次录像持续时间(秒) const size_t MAX_QUEUE_SIZE = 30; // 录像帧队列最大缓存帧数 // ======================================== // 自定义图像帧结构体 // ======================================== struct ImageFrame { std::vector<uint8_t> data; // 存储处理后的图像数据(可能已旋转) MV_FRAME_OUT_INFO_EX info; // 图像元信息(含分辨率、格式等) std::chrono::steady_clock::time_point timestamp; // 时间戳,用于同步或分析 }; // ======================================== // 全局变量 // ======================================== std::atomic<bool> g_bExit(false); // 控制程序退出 std::atomic<bool> g_bIsRecording(false); // 是否正在录像 std::atomic<bool> g_bEnableRotation(true); // ✅ 是否启用图像旋转(默认开启) std::atomic<unsigned int> g_pixelFormat(PixelType_Gvsp_Mono8); std::mutex g_queueMutex; // 保护帧队列的互斥锁 std::queue<ImageFrame> g_frameQueue; // 存放待录制帧的线程安全队列 std::condition_variable g_cvQueue; // 通知录像线程有新帧到来 int g_width = 0, g_height = 0; // 相机原始宽度高度 float g_fps = 25.0f; // 实际帧率,用于录像输出 // ======================================== // 工具函数:获取系统临时目录路径 // ======================================== std::string GetTempPathStr() { char path[MAX_PATH] = { 0 }; GetTempPathA(MAX_PATH, path); return std::string(path); } // ======================================== // 工具函数:递归创建目录 // ======================================== bool CreateDirectoryRecursive(const std::string& dir) { if (CreateDirectoryA(dir.c_str(), nullptr) || GetLastError() == ERROR_ALREADY_EXISTS) return true; size_t pos = dir.find_last_of("\\/"); if (pos != std::string::npos) { std::string parent = dir.substr(0, pos); if (CreateDirectoryRecursive(parent)) { return CreateDirectoryA(dir.c_str(), nullptr) || GetLastError() == ERROR_ALREADY_EXISTS; } } return false; } // ======================================== // 工具函数:检查目录是否可写 // ======================================== bool IsDirectoryWritable(const std::string& dir) { if (!CreateDirectoryRecursive(dir)) return false; std::string testFile = dir + "\\__test_write__.tmp"; FILE* f = nullptr; errno_t err = fopen_s(&f, testFile.c_str(), "w"); if (err == 0 && f) { fclose(f); DeleteFileA(testFile.c_str()); return true; } return false; } // ======================================== // 获取有效输出目录(优先桌面 → 回退到 Temp) // ======================================== std::string GetOutputDirectory() { std::string target(DESKTOP_TEST_DIR); if (IsDirectoryWritable(target)) { printf("✅ Using output directory: %s\n", target.c_str()); return target; } else { printf("❌ Cannot write to %s\n", target.c_str()); std::string temp = GetTempPathStr(); if (IsDirectoryWritable(temp)) { printf("💡 Falling back to: %s\n", temp.c_str()); return temp; } printf("❌ All paths unwritable!\n"); return ""; } } // ======================================== // 打印设备基本信息(IP 或型号) // ======================================== void PrintDeviceInfo(MV_CC_DEVICE_INFO* pstDevInfo) { if (pstDevInfo->nTLayerType == MV_GIGE_DEVICE) { unsigned int ip = pstDevInfo->SpecialInfo.stGigEInfo.nCurrentIp; printf("IP: %d.%d.%d.%d\n", (ip >> 24) & 0xFF, (ip >> 16) & 0xFF, (ip >> 8) & 0xFF, ip & 0xFF); printf("Model: %s\n", pstDevInfo->SpecialInfo.stGigEInfo.chModelName); } else if (pstDevInfo->nTLayerType == MV_USB_DEVICE) { printf("Model: %s\n", pstDevInfo->SpecialInfo.stUsb3VInfo.chModelName); printf("Serial: %s\n", pstDevInfo->SpecialInfo.stUsb3VInfo.chSerialNumber); } } // ======================================== // 设置首选像素格式(RGB8 > BayerRG8 > Mono8) // ======================================== static const unsigned int SUPPORTED_PIXEL_FORMATS[] = { PixelType_Gvsp_RGB8_Packed, PixelType_Gvsp_BayerRG8, PixelType_Gvsp_Mono8 }; static const int NUM_SUPPORTED_FORMATS = 3; int SetPreferredPixelFormat(void* handle, unsigned int& selectedFormat) { for (int i = 0; i < NUM_SUPPORTED_FORMATS; ++i) { unsigned int fmt = SUPPORTED_PIXEL_FORMATS[i]; int nRet = MV_CC_SetEnumValue(handle, "PixelFormat", fmt); if (nRet == MV_OK) { const char* name = ""; switch (fmt) { case PixelType_Gvsp_Mono8: name = "Mono8"; break; case PixelType_Gvsp_BayerRG8: name = "BayerRG8"; break; case PixelType_Gvsp_RGB8_Packed: name = "RGB8"; break; default: name = "Unknown"; } printf("✅ Set PixelFormat to %s\n", name); selectedFormat = fmt; return MV_OK; } } printf("❌ No supported pixel format available!\n"); return 0; } // ======================================== // 对齐图像尺寸(某些相机要求 Width/Height 满足对齐规则) // ======================================== bool AlignImageSize(void* handle, int& width, int& height) { MVCC_INTVALUE stValue = { 0 }; if (MV_CC_GetIntValue(handle, "Width", &stValue) != MV_OK) { printf("❌ Failed to get Width\n"); return false; } width = (stValue.nCurValue / 32) * 32; MV_CC_SetIntValue(handle, "Width", width); printf("🔧 Set Width to %d\n", width); if (MV_CC_GetIntValue(handle, "Height", &stValue) != MV_OK) { printf("❌ Failed to get Height\n"); return false; } height = (stValue.nCurValue / 2) * 2; MV_CC_SetIntValue(handle, "Height", height); printf("🔧 Set Height to %d\n", height); return true; } // ======================================== // 将原始图像数据转换为 OpenCV 可显示的 BGR 格式 // ======================================== cv::Mat ConvertToBGR(const uint8_t* pData, int width, int height, unsigned int pixelFormat) { cv::Mat bgr; switch (pixelFormat) { case PixelType_Gvsp_Mono8: { cv::Mat gray(height, width, CV_8UC1, const_cast<uint8_t*>(pData)); cv::cvtColor(gray, bgr, cv::COLOR_GRAY2BGR); break; } case PixelType_Gvsp_BayerRG8: { cv::Mat bayer(height, width, CV_8UC1, const_cast<uint8_t*>(pData)); cv::cvtColor(bayer, bgr, cv::COLOR_BayerRG2BGR); break; } case PixelType_Gvsp_RGB8_Packed: { cv::Mat rgb(height, width, CV_8UC3, const_cast<uint8_t*>(pData)); cv::cvtColor(rgb, bgr, cv::COLOR_RGB2BGR); break; } default: printf("⚠️ Unsupported pixel format: 0x%x\n", pixelFormat); bgr = cv::Mat::zeros(height, width, CV_8UC3); break; } return bgr; } // ======================================== // 🎥 录像线程函数:从队列取帧并写入 AVI 文件 // ======================================== void RecordFromQueue(const std::string& filePath, int width, int height, int targetFrameCount, float outputFps) { printf("🎥 Recording started: %s\n", filePath.c_str()); printf("🎯 Target frames: %d, Output FPS: %.3f → Duration: %.2f sec\n", targetFrameCount, outputFps, targetFrameCount / outputFps); // 使用 MJPG 编码保存为 .avi 视频 cv::VideoWriter writer(filePath, cv::VideoWriter::fourcc('M', 'J', 'P', 'G'), outputFps, cv::Size(width, height), true); if (!writer.isOpened()) { printf("❌ Failed to open VideoWriter: %s\n", filePath.c_str()); g_bIsRecording = false; return; } int recorded = 0; auto start = std::chrono::steady_clock::now(); try { while (g_bIsRecording && !g_bExit && recorded < targetFrameCount) { std::unique_lock<std::mutex> lock(g_queueMutex); auto timeout = std::chrono::steady_clock::now() + std::chrono::seconds(3); if (g_frameQueue.empty()) { if (g_cvQueue.wait_until(lock, timeout) == std::cv_status::timeout) { printf("📹 Timeout waiting for frame during recording.\n"); break; } } if (!g_frameQueue.empty()) { ImageFrame frame = std::move(g_frameQueue.front()); g_frameQueue.pop(); lock.unlock(); // 提前解锁,避免阻塞采集 cv::Mat bgr = ConvertToBGR(frame.data.data(), frame.info.nWidth, frame.info.nHeight, frame.info.enPixelType); if (bgr.empty()) continue; writer.write(bgr); recorded++; } } } catch (const std::exception& e) { printf("❌ Exception during recording: %s\n", e.what()); } catch (...) { printf("❌ Unknown error during recording.\n"); } writer.release(); auto durationMs = std::chrono::duration_cast<std::chrono::milliseconds>( std::chrono::steady_clock::now() - start).count(); printf("⏹️ Saved video: %s\n", filePath.c_str()); printf("📊 Wrote %d frames at %.3f FPS → Play time: %.2f sec\n", recorded, outputFps, recorded / outputFps); printf("⏱️ Actual process time: %.3f sec\n", durationMs / 1000.0); g_bIsRecording = false; } // ======================================== // 🔧 主工作线程:图像采集 + 旋转控制 + 显示 + 按键响应 // ======================================== unsigned int __stdcall WorkThread(void* pUser) { void* handle = pUser; cv::namedWindow("Live View", cv::WINDOW_AUTOSIZE); MV_FRAME_OUT stImageInfo = { 0 }; int nRet = MV_OK; // 获取并对齐图像尺寸 AlignImageSize(handle, g_width, g_height); // 预分配足够大的缓冲区用于旋转图像存储(按 RGB 最大估算) size_t maxRotatedDataSize = static_cast<size_t>(g_width) * g_height * 3; std::vector<uint8_t> rotatedBuffer(maxRotatedDataSize); // 获取真实帧率 MVCC_FLOATVALUE stFloat = { 0 }; if (MV_CC_GetFloatValue(handle, "ResultingFrameRate", &stFloat) == MV_OK) { g_fps = stFloat.fCurValue; printf("🎯 Actual Camera FPS: %.3f\n", g_fps); } else { printf("⚠️ Could not get FPS, using default %.2f\n", g_fps); } std::string outputDir = GetOutputDirectory(); while (!g_bExit) { // 获取一帧图像(超时 1000ms) nRet = MV_CC_GetImageBuffer(handle, &stImageInfo, 1000); if (nRet == MV_OK) { MV_FRAME_OUT_INFO_EX* pInfo = &stImageInfo.stFrameInfo; // 计算源图像数据大小(不同格式不同字节数) auto GetImageSize = [](int w, int h, unsigned int fmt) -> size_t { switch (fmt) { case PixelType_Gvsp_Mono8: case PixelType_Gvsp_BayerGR8: case PixelType_Gvsp_BayerRG8: case PixelType_Gvsp_BayerGB8: case PixelType_Gvsp_BayerBG8: return static_cast<size_t>(w) * h; case PixelType_Gvsp_RGB8_Packed: return static_cast<size_t>(w) * h * 3; case PixelType_Gvsp_RGBA8_Packed: return static_cast<size_t>(w) * h * 4; default: return static_cast<size_t>(w) * h; } }; size_t srcLen = GetImageSize(pInfo->nWidth, pInfo->nHeight, pInfo->enPixelType); // 输出图像尺寸与数据 int outWidth = pInfo->nWidth; int outHeight = pInfo->nHeight; std::vector<uint8_t> imgData; // ✅ 判断是否需要执行旋转 bool shouldRotate = g_bEnableRotation.load(); if (shouldRotate) { // 👉 初始化旋转参数结构体(必须清零!) MV_CC_ROTATE_IMAGE_PARAM rotateParam ; rotateParam.enPixelType = static_cast<MvGvspPixelType>(pInfo->enPixelType); rotateParam.nWidth = pInfo->nWidth; rotateParam.nHeight = pInfo->nHeight; rotateParam.pSrcData = stImageInfo.pBufAddr; rotateParam.nSrcDataLen = static_cast<unsigned int>(srcLen); rotateParam.pDstBuf = rotatedBuffer.data(); rotateParam.nDstBufSize = static_cast<unsigned int>(rotatedBuffer.size()); rotateParam.enRotationAngle = MV_IMAGE_ROTATE_90; // 90度:新宽=原高,新高=原宽 // 🔄 调用 SDK 接口执行旋转 nRet = MV_CC_RotateImage(handle, &rotateParam); if (nRet != MV_OK) { printf("❌ MV_CC_RotateImage failed! [0x%x]\n", nRet); MV_CC_FreeImageBuffer(handle, &stImageInfo); continue; } // ✅ 使用 SDK 返回的实际输出尺寸 outWidth = rotateParam.nWidth; outHeight = rotateParam.nHeight; size_t dataSize = static_cast<size_t>(rotateParam.nDstBufLen); // 安全检查 if (dataSize == 0 || dataSize > rotatedBuffer.size()) { printf("❌ Invalid rotated image size: %zu bytes\n", dataSize); MV_CC_FreeImageBuffer(handle, &stImageInfo); continue; } // 提取有效数据 imgData.assign(rotatedBuffer.begin(), rotatedBuffer.begin() + dataSize); } else { // ❌ 不旋转:直接复制原始图像 size_t dataSize = srcLen; imgData.resize(dataSize); memcpy(imgData.data(), stImageInfo.pBufAddr, dataSize); // 尺寸不变 } // ✅ 转换为 BGR 并显示 cv::Mat bgr = ConvertToBGR(imgData.data(), outWidth, outHeight, pInfo->enPixelType); if (!bgr.empty()) { // 添加状态文字 std::string status = g_bIsRecording ? "🔴 RECORDING..." : "✅ LIVE STREAM"; std::string rotation = g_bEnableRotation.load() ? "🔄 ROTATION: ON" : "➡️ ROTATION: OFF"; cv::putText(bgr, status, cv::Point(20, 40), cv::FONT_HERSHEY_SIMPLEX, 1.0, cv::Scalar(0, 255, 0), 2); cv::putText(bgr, rotation, cv::Point(20, 80), cv::FONT_HERSHEY_SIMPLEX, 0.8, cv::Scalar(255, 255, 0), 2); cv::imshow("Live View", bgr); } // ⌨️ 按键控制 int key = cv::waitKey(1); if (key == 27 || key == 'q' || key == 'Q') { g_bExit = true; } else if ((key == 'r' || key == 'R') && !g_bIsRecording) { if (outputDir.empty()) { printf("❌ No valid output directory!\n"); } else { g_bIsRecording = true; int targetFrameCount = static_cast<int>(RECORD_DURATION_SECONDS * g_fps); auto now = std::chrono::system_clock::now(); auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()) % 1000; std::time_t t = std::chrono::system_clock::to_time_t(now); struct tm tm; localtime_s(&tm, &t); char filePath[512]; sprintf_s(filePath, "%s\\rec_%04d%02d%02d_%02d%02d%02d_%03dms.avi", outputDir.c_str(), tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, (int)ms.count()); try { std::thread t(RecordFromQueue, std::string(filePath), outWidth, outHeight, targetFrameCount, g_fps); t.detach(); // 非阻塞运行 } catch (const std::exception& e) { printf("❌ Failed to start thread: %s\n", e.what()); g_bIsRecording = false; } } } else if (key == 't' || key == 'T') { // 🔁 切换旋转使能状态 bool newState = !g_bEnableRotation.load(); g_bEnableRotation.store(newState); printf("🔄 Rotation toggled: %s\n", newState ? "ON" : "OFF"); } // 📥 若正在录像,则将当前帧加入队列 if (g_bIsRecording) { std::lock_guard<std::mutex> lock(g_queueMutex); while (g_frameQueue.size() >= MAX_QUEUE_SIZE) { g_frameQueue.pop(); // 丢弃旧帧防止积压 } ImageFrame frame; frame.data = std::move(imgData); frame.info = *pInfo; frame.info.nWidth = outWidth; frame.info.nHeight = outHeight; frame.timestamp = std::chrono::steady_clock::now(); g_frameQueue.push(std::move(frame)); g_cvQueue.notify_one(); // 唤醒录像线程 } // ✅ 释放图像缓冲区(非常重要!) MV_CC_FreeImageBuffer(handle, &stImageInfo); } else { printf("⚠️ Timeout getting frame from camera.\n"); } } // 清理资源 { std::lock_guard<std::mutex> lock(g_queueMutex); g_bIsRecording = false; } g_cvQueue.notify_all(); cv::destroyAllWindows(); return 0; } // ======================================== // 🚀 主函数 // ======================================== int main() { int nRet = MV_OK; void* handle = nullptr; HANDLE hThread = nullptr; do { // 初始化 SDK nRet = MV_CC_Initialize(); if (nRet != MV_OK) { printf("❌ Initialize SDK failed! [0x%x]\n", nRet); break; } // 枚举设备 MV_CC_DEVICE_INFO_LIST stDevList = { 0 }; nRet = MV_CC_EnumDevices(MV_GIGE_DEVICE | MV_USB_DEVICE, &stDevList); if (nRet != MV_OK || stDevList.nDeviceNum == 0) { printf("❌ No devices found! [0x%x]\n", nRet); break; } printf("✅ Found %d device(s):\n", stDevList.nDeviceNum); for (unsigned int i = 0; i < stDevList.nDeviceNum; ++i) { printf(" [%d]: ", i); PrintDeviceInfo(stDevList.pDeviceInfo[i]); } unsigned int index = 0; printf("👉 Input camera index (0-%d): ", stDevList.nDeviceNum - 1); scanf_s("%d", &index); if (index >= stDevList.nDeviceNum) { printf("❌ Invalid index!\n"); break; } // 创建句柄 nRet = MV_CC_CreateHandle(&handle, stDevList.pDeviceInfo[index]); if (nRet != MV_OK) { printf("❌ CreateHandle failed! [0x%x]\n", nRet); break; } // 打开设备 nRet = MV_CC_OpenDevice(handle); if (nRet != MV_OK) { printf("❌ OpenDevice failed! [0x%x]\n", nRet); break; } // GigE 网络优化 if (stDevList.pDeviceInfo[index]->nTLayerType == MV_GIGE_DEVICE) { int packetSize = MV_CC_GetOptimalPacketSize(handle); if (packetSize > 0) { MV_CC_SetIntValueEx(handle, "GevSCPSPacketSize", packetSize); } } // 关闭触发模式,进入连续采集 MV_CC_SetEnumValue(handle, "TriggerMode", 0); // 设置首选像素格式 unsigned int chosenFormat = 0; if (SetPreferredPixelFormat(handle, chosenFormat) != MV_OK) break; g_pixelFormat = chosenFormat; // 开始抓图 nRet = MV_CC_StartGrabbing(handle); if (nRet != MV_OK) { printf("❌ StartGrabbing failed! [0x%x]\n", nRet); break; } // 启动工作线程 unsigned int threadId = 0; hThread = (HANDLE)_beginthreadex(nullptr, 0, WorkThread, handle, 0, &threadId); if (!hThread) { printf("❌ Failed to create work thread!\n"); break; } printf("\n📌 Instructions:\n"); printf(" ➡ Press 'R' to record %.1f seconds video\n", RECORD_DURATION_SECONDS); printf(" ➡ Press 'T' to toggle rotation (current: %s)\n", g_bEnableRotation.load() ? "ON" : "OFF"); printf(" ➡ Live preview will NOT freeze during recording!\n"); printf(" ➡ Output FPS matches actual camera FPS\n"); printf(" ➡ File name includes milliseconds to avoid overwrite\n"); printf(" ➡ Press 'Q' or ESC to exit\n\n"); while (!g_bExit) { Sleep(100); } } while (false); // 结束流程 g_bExit = true; if (hThread) { WaitForSingleObject(hThread, 5000); CloseHandle(hThread); } if (handle) { MV_CC_StopGrabbing(handle); MV_CC_CloseDevice(handle); MV_CC_DestroyHandle(handle); } MV_CC_Finalize(); printf("👋 Program exited. Press any key to close.\n"); _getch(); return 0; } 将该C++DEMO修改为java语言
11-22
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值