• 概要
  • SERファイルまたは撮影画像から流星を検出します。
    SERはPCで処理、撮影画像はRaspberryPiで処理することを考えています。



  • リアルタイム版デモ


  • バッチ版デモ
  • マスク(作成のしやすさから黒塗エリアを有効範囲にしています)

    検出データの積算(一覧のしやすさから出力しています)

    検出データ(最終的な目視確認のため)

    一覧表
    Frame NoMeteor NoPathFrame No In FileDateTimeDetection pix rate
    00010001imx462_20221215/1024x1024_221215_192359_544423.ser01122022-12-1510:24:55.796513UTC0.0020%
    00020001imx462_20221215/1024x1024_221215_192359_544423.ser01132022-12-1510:24:56.248583UTC0.0030%
    00030001imx462_20221215/1024x1024_221215_192359_544423.ser01142022-12-1510:24:56.800591UTC0.0027%
    00040001imx462_20221215/1024x1024_221215_192359_544423.ser01152022-12-1510:24:57.305007UTC0.0012%
    00050002imx462_20221215/1024x1024_221215_195259_906508.ser00872022-12-1510:53:43.548024UTC0.0010%
    00060003imx462_20221215/1024x1024_221215_195259_906508.ser01152022-12-1510:53:57.603212UTC0.0010%
    00070004imx462_20221215/1024x1024_221215_221304_871616.ser00392022-12-1513:13:24.458220UTC0.0063%
    00080004imx462_20221215/1024x1024_221215_221304_871616.ser00402022-12-1513:13:25.2580UTC0.0062%
    00090005imx462_20221215/1024x1024_221215_231307_023956.ser00232022-12-1514:13:18.604950UTC0.0013%
    00100006imx462_20221215/1024x1024_221215_231307_023956.ser00262022-12-1514:13:20.76255UTC0.0011%
    00110007imx462_20221215/1024x1024_221215_231307_023956.ser00292022-12-1514:13:21.611667UTC0.0010%
    00120008imx462_20221215/1024x1024_221215_232107_587998.ser00532022-12-1514:21:34.197174UTC0.0026%
    00130009imx462_20221215/1024x1024_221215_234208_377796.ser00482022-12-1514:42:32.488631UTC0.0030%
    00140010imx462_20221215/1024x1024_221216_013112_030324.ser00762022-12-1516:31:50.194727UTC0.0011%
    00150010imx462_20221215/1024x1024_221216_013112_030324.ser00772022-12-1516:31:50.660837UTC0.0020%
    00160011imx462_20221215/1024x1024_221216_032415_439085.ser00132022-12-1518:24:21.925750UTC0.0010%
    00170011imx462_20221215/1024x1024_221216_032415_439085.ser00142022-12-1518:24:22.477900UTC0.0010%
    00180012imx462_20221215/1024x1024_221216_032415_439085.ser00162022-12-1518:24:23.471161UTC0.0011%
    00190012imx462_20221215/1024x1024_221216_032415_439085.ser00172022-12-1518:24:23.935767UTC0.0010%
    00200013imx462_20221215/1024x1024_221216_040517_052725.ser00452022-12-1519:05:39.684994UTC0.0033%
    00210013imx462_20221215/1024x1024_221216_040517_052725.ser00462022-12-1519:05:40.148288UTC0.0035%


  • 検出アルゴリズム
    1. ひとつ前の画像と差の絶対値を取ります。
    2. ノイズを除去します。
    3. 検出エリアの指定があればそれ以外を除去します。
    4. 2値化します。
    5. 閾値以上の面積比を計算します。
    6. 標準偏差を計算しあます。
    7. 範囲内の場合、検出フラグを立てます。
    8. 連続フレームでない場合は、検出通知します。


  • 画像処理関数
  • RAWで処理する関数を作成しました。
    void InitImage(unsigned short *dst, int width, int height, unsigned short val);
    void SLImage(unsigned short *dst, unsigned short *src, int width, int height, int num);
    void SDImage(unsigned short *dst, unsigned short *src, int width, int height, int num);
    void AvrImage(unsigned short *dst, unsigned short *src, int width, int height, int num);
    void SubImage(unsigned short *dst, unsigned short *src, int width, int height, int num);
    void DifImage(unsigned short *dst, unsigned short *src1, unsigned short *src2, int width, int height, int num);
    void AbsImage(unsigned short *dst, unsigned short *src, int width, int height, int num);
    void CopyImage(unsigned short *dst, unsigned short *src, int width, int height, int num);
    float BinzImage(unsigned short *dst, unsigned short threshold, int width, int height);
    float CountImage(unsigned short *dst, unsigned short threshold, int width, int height);
    void SDeviImage(unsigned short *dst, unsigned short threshold, int width, int height, double *ax, double *ay, double *sx, double *sy);
    void MaxMinImage(unsigned short *dst, unsigned short threshold, int width, int height, int *minx, int *miny, int *maxx, int *maxy);
    void NRImage(unsigned short *dst, unsigned short threshold, int width, int height);
    void WBImageRG(unsigned short *src, int width, int height);
    


  • バッチ版ソース
  • int readSer(char *path, float lthresh, float ratio){
        FILE *fpSer;
        serHeader SerHeader;
        char tmpc[41];
    
        printf("%s\n",path);
    
        fpSer = fopen64(path, "rb");
        int r = fread(&SerHeader.FileID, 1, sizeof(SerHeader.FileID), fpSer);
        strncpy(tmpc, SerHeader.FileID, 14);
        tmpc[14]='\0';
        r = fread(&SerHeader.LuID, 1, sizeof(SerHeader.LuID), fpSer);
        r = fread(&SerHeader.ColorID, 1, sizeof(SerHeader.ColorID), fpSer);
        r = fread(&SerHeader.LittleEndian, 1, sizeof(SerHeader.LittleEndian), fpSer);
        r = fread(&SerHeader.ImageWidth, 1, sizeof(SEEK_CUR), fpSer);
        r = fread(&SerHeader.ImageHeight, 1, sizeof(SerHeader.ImageHeight), fpSer);
        r = fread(&SerHeader.PixelDepthPerPlane, 1, sizeof(SerHeader.PixelDepthPerPlane), fpSer);
        r = fread(&SerHeader.FrameCount, 1, sizeof(SerHeader.FrameCount), fpSer);
        r = fread(&SerHeader.Observer, 1, sizeof(SerHeader.Observer), fpSer);
        strncpy(tmpc, SerHeader.Observer, 40);
        tmpc[40]='\0';
        r = fread(&SerHeader.Instrument, 1, sizeof(SerHeader.Instrument), fpSer);
        strncpy(tmpc, SerHeader.Instrument, 40);
        tmpc[40]='\0';
        r = fread(&SerHeader.Telescope, 1, sizeof(SerHeader.Telescope), fpSer);
        strncpy(tmpc, SerHeader.Telescope, 40);
        tmpc[40]='\0';//
        int year, month, day, hour, minute, second, microsec;
        r = fread(&SerHeader.DateTime, 1, sizeof(SerHeader.DateTime), fpSer);
        timestamp2date(SerHeader.DateTime, &year, &month, &day, &hour, &minute, &second, µsec);
        r = fread(&SerHeader.DateTime_UTC, 1, sizeof(SerHeader.DateTime_UTC), fpSer);
        timestamp2date(SerHeader.DateTime_UTC, &year, &month, &day, &hour, &minute, &second, µsec);
        unsigned long TrailerDate;
        unsigned long *TrailerDateP = (unsigned long *)malloc(SerHeader.FrameCount * sizeof(TrailerDate));
        fseek(fpSer, -1*SerHeader.FrameCount * sizeof(TrailerDate), SEEK_END);
        for(int i = 0; i < SerHeader.FrameCount; i++){
            r = fread(&TrailerDate, 1, sizeof(TrailerDate), fpSer);
            if(r < 1){
                printf("trailer read error\n");
                free(TrailerDateP);
                return(1);
            }
            TrailerDateP[i] = TrailerDate;
            timestamp2date(TrailerDate, &year, &month, &day, &hour, &minute, &second, µsec);
        }
    
        fseek(fpSer, 178, SEEK_SET);
        int RawSize = SerHeader.ImageWidth * SerHeader.ImageHeight * SerHeader.PixelDepthPerPlane / 8;
        cv::Mat rgb8BitMat(SerHeader.ImageHeight, SerHeader.ImageWidth, CV_8UC3);			// RGB用バッファを用意
        cv::Mat rgb8BitMat2(SerHeader.ImageHeight/2, SerHeader.ImageWidth/2, CV_8UC3);			// RGB用バッファを用意
        int oc = -1;
        for(int i=0; i<SerHeader.FrameCount; i++){
            Temp = OldImage;
            OldImage = NewImage;
            NewImage = Temp;
            int r = fread(NewImage, sizeof(char), RawSize, fpSer);
            if(DarkImage != NULL)
                SubImage(NewImage, DarkImage, SerHeader.ImageWidth, SerHeader.ImageHeight, 0);
            WBImageRG((unsigned short *)NewImage, SerHeader.ImageWidth, SerHeader.ImageWidth);
            if(mask_r.data != NULL)
                bmask(mask_r, (unsigned short *)NewImage);
            if(i == 0)//
                continue;
            if(r < RawSize){
                printf("image read error\n");
                break;
            }
            DifImage(DffImage, NewImage, OldImage, SerHeader.ImageWidth, SerHeader.ImageHeight, 0);
            NRImage(DffImage, 0x580, , SerHeader.ImageHeight);
            if(mask_d.data != NULL)
                bmask(mask_d, DffImage);
            float lrate = BinzImage(DffImage, 0x580, SerHeader.ImageWidth, SerHeader.ImageHeight) * 100;
            if(lrate < 0.03 && lrate > lthresh){
                double ax, ay, sx, sy;
                SDeviImage(DffImage, 0x580, SerHeader.ImageWidth, SerHeader.ImageHeight, &ax, &ay, &sx, &sy);
                if(sx/(double)SerHeader.ImageWidth < 0.01 || sy/(double)SerHeader.ImageHeight < 0.01){
                    fc++;
                    if(i != oc + 1){
                        mc++;
                        printf("Meteor No. %d, frame No. %d\n", mc, i);
                    }
    
                    int minx, miny, maxx, maxy;
                    MaxMinImage(DffImage, 0x580, SerHeader.ImageWidth, SerHeader.ImageHeight, &minx, &miny, &maxx, &maxy);
    
                    oc = i;
                    cv::Mat OutImage(cv::Size(SerHeader.ImageWidth * 2, SerHeader.ImageHeight), CV_8UC3);   // 合成後のイメージ作成
                    cv::Mat imageLeft(OutImage, cv::Rect(                    0, 0, rgb8BitMat.cols, rgb8BitMat.rows));  // コピーする領域左側
                    cv::Mat imageRight(OutImage, cv::Rect(SerHeader.ImageWidth, 0, rgb8BitMat.cols, rgb8BitMat.rows));  // コピーする領域右側
    
                    cv::Mat bayer16BitMat(SerHeader.ImageHeight, SerHeader.ImageWidth, CV_16UC1, NewImage);	// 16bit用バッファを用意して取り込み
                    cv::Mat bayer8BitMat = bayer16BitMat.clone();                   // 8bit用バッファを用意
                    bayer8BitMat.convertTo(bayer8BitMat, CV_8UC1, 0.0625*ratio);	// 8bitに変換
                    cv::cvtColor(bayer8BitMat, rgb8BitMat, cv::COLOR_BayerRG2RGB);	// デベイヤー
                    rgb8BitMat.copyTo(imageRight);                                  // 右側にコピーして表示させる
    
                    cv::rectangle(imageRight, cv::Point(ax-1, ay-1), cv::Point(ax+1, ay+1), CV_RGB(255,0,0), 2, 10, 0);           // 赤の矩形
                    cv::rectangle(imageRight, cv::Point(ax-sx, ay-sy), cv::Point(ax+sx, ay+sy), CV_RGB(0,0,255), 2, 10, 0);       // 緑の矩形
                    cv::rectangle(imageRight, cv::Point(minx, miny), cv::Point(maxx, maxy), CV_RGB(0,255,0), 2, 10, 0);           // 緑の矩形
    
                    cv::Mat bayer16BitMat2(SerHeader.ImageHeight, SerHeader.ImageWidth, CV_16UC1, DffImage); // 16bit用バッファを用意して取り込み
                    bayer8BitMat = bayer16BitMat2.clone();                          // 8bit用バッファを用意
                    bayer8BitMat.convertTo(bayer8BitMat, CV_8UC1, 0.0625*ratio);	// 8bitに変換
                    cv::cvtColor(bayer8BitMat, rgb8BitMat, cv::COLOR_BayerRG2RGB);	// デベイヤー
    
                    timestamp2date(TrailerDateP[i], &year, &month, &day, &hour, &minute, &second, µsec);
                    sprintf(str, "%4.4d %4.4d %4.4d:%2.4f%", fc, mc, i, lrate);
                    cv::putText(rgb8BitMat, str, cv::Point(20, 50),cv::FONT_HERSHEY_SIMPLEX, 1, CV_RGB(200,200,200), 1);
                    sprintf(str, "%4.4d-%2.2d-%2.2d %2.2d:%2.2d:%2.2d.%4.4dUTC", year, month, day, hour, minute, second, microsec);
                    cv::putText(rgb8BitMat, str, cv::Point(20, 100),cv::FONT_HERSHEY_SIMPLEX, 1, CV_RGB(200,200,200), 1);
                    sprintf(str, "ax:%04.0lf ay:%04.0lf sx:%04.0lf sy:%04.0lf", ax, ay, sx, sy);
                    cv::putText(rgb8BitMat, str, cv::Point(20, SerHeader.ImageHeight - 20),cv::FONT_HERSHEY_SIMPLEX, 1, CV_RGB(200,200,200), 1);
                    rgb8BitMat.copyTo(imageLeft);                                   // 左側にコピーして表示させる
    
                    fprintf(fpLog,"%4.4d,%4.4d,%s,%4.4d,%4.4d-%2.2d-%2.2d,%2.2d:%2.2d:%2.2d.%4.4dUTC,%2.4f%\n"
                                , fc, mc, path, i, year, month, day, hour, minute, second, microsec, lrate);
    
                    writer << OutImage;  										// 画像 image を動画ファイルへ書き出す
                    cv::resize(OutImage, OutImage, cv::Size(), 0.5, 0.5);		// 縮小
                    writer2 << OutImage;  										// 画像 image を動画ファイルへ書き出す
    
                    SLImage(StkImage, DffImage, SerHeader.ImageWidth, SerHeader.ImageHeight, 0);
                }
            }
        }
        free(TrailerDateP);
        fclose(fpSer);
        return 0;
    }
    


  • リアルタイム版ソース
  •     //-------------------------------------------------------------------
        // 画像を読み込み,表示する
        //-------------------------------------------------------------------
        timeout_counter = 0;
        frame_count = oc = 0;
        st = GetTime();
    
        zoom_setMouseCallback((char *)"se_cam", 1/(float)div);
    
        while (1) {
            if (ReadRAW(RawImage) == 0) {
                if (++timeout_counter > TIMEOUT) {
                    fprintf(stderr, "sensorlaunch: time out.\n");
                    return  1;
                }
                usleep(500);
                continue;
            }
            frame_count++;
            timeout_counter = 0;
    
            unsigned char *Temp = BinImage3;
            BinImage3 = BinImage2;
            BinImage2 = Temp;
            CnvertImage(BinImage2, RawImage, RawSize, rawbit, raw8_flag);
            if(mask_r.data != NULL)
                bmask(mask_r, (unsigned short *)BinImage2);
    
            if(mode == SERD_MODE){
                DifImage((unsigned short *)DiffImage, (unsigned short *)BinImage2, (unsigned short *)BinImage3, width, height, 0);
                NRImage((unsigned short *)DiffImage, bthresh >> (16-bpp), width, height);
                if(mask_d.data != NULL)
                    bmask(mask_d, (unsigned short *)DiffImage);
                BinzImage((unsigned short *)DiffImage, bthresh >> (16-bpp), width, height);
                float lrate = CountImage((unsigned short *)DiffImage, bthresh >> (16-bpp), width, height) * 100;
                if(lrate > 0.001){
                    double ax, ay, sx, sy;
                    SDeviImage((unsigned short *)DiffImage, bthresh >> (16-bpp), width, height, &ax, &ay, &sx, &sy);
                    if(sx/(double)width < 0.1 || sy/(double)height < 0.1){
                        printf("%5d:Diff Detect. sx:%04.0lf sy:%04.0lf\n", ++diffcount, sx, sy);
                        diff = ON;
                        if(frame_count != oc +1)
                            system("~/detect_voice.sh");
                        oc = frame_count;
                    }
                }
    
                // 差分表示用
                cv::Mat bayerXBitMat2(height, width, depth, DiffImage);         // Xbit用バッファを用意して取り込み
                cv::Mat bayer8BitMat2 = bayerXBitMat2.clone();                  // 8bit用バッファを用意
                resize(bayer8BitMat2, bayer8BitMat2, cv::Size(), 1/(float)div, 1/(float)div);       // 縮小
                cv::imshow("ImageDiff", bayer8BitMat2);                          // ウィンドウに画像を表示する
    
                // 差分積算表示用
                if(diff == ON){
                    SLImage((unsigned short *)StkImage, (unsigned short *)DiffImage, width, height, 0);
                    cv::Mat bayer8BitMat3(height, width, CV_16UC1, StkImage);   // 16bit用バッファを用意して取り込み
                    bayer8BitMat3.convertTo(bayer8BitMat3, CV_8UC1, 0.0625*2);  // 8bitに変換
                    sprintf(str, "%d differences detected", diffcount);
                    cv::putText(bayer8BitMat3, str, cv::Point(20, 50),cv::FONT_HERSHEY_SIMPLEX, 1, CV_RGB(200,200,200), 1);
                    resize(bayer8BitMat3, bayer8BitMat3, cv::Size(), 1/(float)div, 1/(float)div);   // 縮小
                    cv::imshow("ImageDiffStack", bayer8BitMat3);                 // ウィンドウに画像を表示する
                }
            }
    
            // メインのRAW表示用
            cv::Mat bayerXBitMat(height, width, depth, BinImage2);              // Xbit用バッファを用意して取り込み
            cv::Mat bayer8BitMat = bayerXBitMat.clone();                        // 8bit用バッファを用意
            if(!raw8_flag){
                bayer8BitMat.convertTo(bayer8BitMat, CV_8UC1, 0.02*4);          // 8bitに変換
            }
            cv::Mat rgb8BitMat(height, width, CV_8UC3);                         // 保存用RGBバッファを用意
            cv::Mat dsp8BitMat(height, width, CV_8UC3);	                        // 表示用RGBバッファを用意
            cv::cvtColor(bayer8BitMat, rgb8BitMat, cv::COLOR_BayerRG2RGB);      // デベイヤー
            resize(rgb8BitMat, dsp8BitMat, cv::Size(), 1/(float)div, 1/(float)div);     // 縮小
            zoom_imshow(rgb8BitMat, dsp8BitMat, 2.0);                           // 拡大表示ウィンドウに画像を表示する
            cv::imshow("se_cam", dsp8BitMat);                                   // ウィンドウに画像を表示する
    
            // キー入力の処理
    


  • 参考サイト