001// Copyright (c) FIRST and other WPILib contributors.
002// Open Source Software; you can modify and/or share it under the terms of
003// the WPILib BSD license file in the root directory of this project.
004
005package edu.wpi.first.cscore;
006
007import edu.wpi.first.util.PixelFormat;
008import org.opencv.core.Mat;
009
010/**
011 * A source that represents a video camera. These sources require the WPILib OpenCV builds. For an
012 * alternate OpenCV, see the documentation how to build your own with RawSource.
013 */
014public class CvSource extends ImageSource {
015  /**
016   * Create an OpenCV source.
017   *
018   * @param name Source name (arbitrary unique identifier)
019   * @param mode Video mode being generated
020   */
021  public CvSource(String name, VideoMode mode) {
022    super(
023        CameraServerJNI.createRawSource(
024            name, true, mode.pixelFormat.getValue(), mode.width, mode.height, mode.fps));
025    OpenCvLoader.forceStaticLoad();
026  }
027
028  /**
029   * Create an OpenCV source.
030   *
031   * @param name Source name (arbitrary unique identifier)
032   * @param pixelFormat Pixel format
033   * @param width width
034   * @param height height
035   * @param fps fps
036   */
037  public CvSource(String name, PixelFormat pixelFormat, int width, int height, int fps) {
038    super(CameraServerJNI.createRawSource(name, true, pixelFormat.getValue(), width, height, fps));
039    OpenCvLoader.forceStaticLoad();
040  }
041
042  /**
043   * Put an OpenCV image and notify sinks
044   *
045   * <p>The image format is guessed from the number of channels. The channel mapping is as follows.
046   * 1: kGray 2: kYUYV 3: BGR 4: BGRA Any other channel numbers will throw an error. If your image
047   * is an in alternate format, use the overload that takes a PixelFormat.
048   *
049   * @param image OpenCV Image
050   */
051  public void putFrame(Mat image) {
052    // We only support 8 bit channels
053    boolean cleanupRequired = false;
054    Mat finalImage;
055    if (image.depth() == 0) {
056      finalImage = image;
057    } else {
058      finalImage = new Mat();
059      image.convertTo(finalImage, 0);
060      cleanupRequired = true;
061    }
062
063    try {
064      int channels = finalImage.channels();
065      PixelFormat format;
066      if (channels == 1) {
067        // 1 channel is assumed Graysacle
068        format = PixelFormat.kGray;
069      } else if (channels == 2) {
070        // 2 channels is assumed YUYV
071        format = PixelFormat.kYUYV;
072      } else if (channels == 3) {
073        // 3 channels is assumed BGR
074        format = PixelFormat.kBGR;
075      } else if (channels == 4) {
076        // 4 channels is assumed BGRA
077        format = PixelFormat.kBGRA;
078      } else {
079        throw new VideoException("Unable to get pixel format for " + channels + " channels");
080      }
081
082      putFrame(finalImage, format, true);
083    } finally {
084      if (cleanupRequired) {
085        finalImage.release();
086      }
087    }
088  }
089
090  /**
091   * Put an OpenCV image and notify sinks.
092   *
093   * <p>The format of the Mat must match the PixelFormat. You will corrupt memory if they dont. With
094   * skipVerification false, we will verify the number of channels matches the pixel format. If
095   * skipVerification is true, this step is skipped and is passed straight through.
096   *
097   * @param image OpenCV image
098   * @param format The pixel format of the image
099   * @param skipVerification skip verifying pixel format
100   */
101  public void putFrame(Mat image, PixelFormat format, boolean skipVerification) {
102    // We only support 8-bit images, convert if necessary
103    boolean cleanupRequired = false;
104    Mat finalImage;
105    if (image.depth() == 0) {
106      finalImage = image;
107    } else {
108      finalImage = new Mat();
109      image.convertTo(finalImage, 0);
110      cleanupRequired = true;
111    }
112
113    try {
114      if (!skipVerification) {
115        verifyFormat(finalImage, format);
116      }
117      long step = image.step1() * image.elemSize1();
118      CameraServerJNI.putRawSourceFrameData(
119          m_handle,
120          finalImage.dataAddr(),
121          (int) finalImage.total() * finalImage.channels(),
122          finalImage.width(),
123          finalImage.height(),
124          (int) step,
125          format.getValue());
126
127    } finally {
128      if (cleanupRequired) {
129        finalImage.release();
130      }
131    }
132  }
133
134  private void verifyFormat(Mat image, PixelFormat pixelFormat) {
135    int channels = image.channels();
136    switch (pixelFormat) {
137      case kBGR:
138        if (channels == 3) {
139          return;
140        }
141        break;
142      case kBGRA:
143        if (channels == 4) {
144          return;
145        }
146        break;
147      case kGray:
148        if (channels == 1) {
149          return;
150        }
151        break;
152      case kRGB565:
153        if (channels == 2) {
154          return;
155        }
156        break;
157      case kUYVY:
158        if (channels == 2) {
159          return;
160        }
161        break;
162      case kY16:
163        if (channels == 2) {
164          return;
165        }
166        break;
167      case kYUYV:
168        if (channels == 2) {
169          return;
170        }
171        break;
172      case kMJPEG:
173        if (channels == 1) {
174          return;
175        }
176        break;
177      default:
178        break;
179    }
180  }
181}