やったもん勝ち

主にプログラミングのこと。生産性向上の某とかも。

顔認識・画像認識ライブラリAPIを使ってみる

経緯

個人的な趣味で、顔認識のAPIを使いたいなと思い立ちました。
OpenCVで自前でできないかなーとも思ったのですが、なかなか難しいっぽく、諦めてAPIを使うという選択になった次第です。

どこがAPIを提供しているのか、以下のリンクがとても参考になりました。
qiita.com

このリンクをもとに、それぞれ調べてみました。
調べる際の観点は「口の開閉が判別できるか」「その精度は確かか」という2点で調査をしました。
なお、調査したのは2017年の年末頃なので、この記事を書いている時点ですでに仕様が更新されている可能性があります。

目次

detectFace()

detectFace(); - 顔検出Webサービス → 2018.04.04にサービス提供終了になりました。

何より簡単に使えて、しかも無料というのがいい。 こんな感じのページ。
f:id:benzenetarou:20171125232836p:plain
このAPIでは50個の特徴量を返してくれるみたいだ。
f:id:benzenetarou:20171125232850p:plain
その結果、笑ってるか怒ってるかとかを判定したいと思ったらこっちで別途実装しなきゃいけないみたいだ。
それはそれで問題はないけども。

丁寧にサンプルページがあるので、実装する前に雰囲気確かめられる。
f:id:benzenetarou:20171125233223p:plain 早速でもページから画像を投げてみる。

帰ってくるxmlはこんな感じ

<?xml version='1.0'?>
<faces>
  <face id='0'>
    <bounds x='328' y='550' width='100' height='100'/>
    <right-eye x='365' y='591'/>
  </face>
  <face id='1'>
    <bounds x='292' y='549' width='106' height='106'/>
  </face>
  <face id='2'>
    <bounds x='706' y='256' width='709' height='709'/>
    <right-eye x='907' y='538'/>
    <left-eye x='1196' y='527'/>
    <features s-avg='0.83' s-min='0.66' s-max='0.95'>
      <point id='PR' x='921' y='536' s='0.80'/>
      <point id='PL' x='1192' y='525' s='0.94'/>
      <point id='BR2' x='1004' y='447' s='0.67'/>
      <point id='BL2' x='1111' y='431' s='0.82'/>
      <point id='N1' x='1057' y='531' s='0.78'/>
      <point id='N5' x='1064' y='689' s='0.86'/>
      <point id='M1' x='1068' y='796' s='0.85'/>
      <point id='M5' x='1071' y='895' s='0.74'/>
      <point id='M3' x='965' y='831' s='0.74'/>
      <point id='M7' x='1176' y='830' s='0.82'/>
      <point id='EL4' x='1257' y='534' s='0.93'/>
      <point id='EL1' x='1139' y='545' s='0.92'/>
      <point id='ER1' x='969' y='556' s='0.82'/>
      <point id='ER4' x='871' y='538' s='0.84'/>
      <point id='M2' x='1011' y='797' s='0.87'/>
      <point id='M8' x='1109' y='793' s='0.91'/>
      <point id='M4' x='1014' y='882' s='0.71'/>
      <point id='M6' x='1145' y='872' s='0.75'/>
      <point id='BR3' x='948' y='428' s='0.70'/>
      <point id='BL3' x='1187' y='409' s='0.84'/>
      <point id='N2' x='965' y='706' s='0.87'/>
      <point id='N4' x='1149' y='703' s='0.92'/>
      <point id='F1' x='1043' y='196' s='0.70'/>
      <point id='F2' x='842' y='260' s='0.89'/>
      <point id='F10' x='1252' y='286' s='0.69'/>
      <point id='ER2' x='949' y='525' s='0.82'/>
      <point id='ER3' x='891' y='522' s='0.81'/>
      <point id='ER5' x='897' y='560' s='0.83'/>
      <point id='ER6' x='936' y='567' s='0.81'/>
      <point id='EL2' x='1164' y='508' s='0.92'/>
      <point id='EL3' x='1231' y='508' s='0.93'/>
      <point id='EL5' x='1229' y='547' s='0.94'/>
      <point id='EL6' x='1170' y='551' s='0.92'/>
      <point id='BR1' x='994' y='489' s='0.72'/>
      <point id='BR4' x='890' y='436' s='0.79'/>
      <point id='BR5' x='841' y='492' s='0.82'/>
      <point id='BR6' x='919' y='449' s='0.77'/>
      <point id='BL1' x='1111' y='468' s='0.81'/>
      <point id='BL4' x='1240' y='414' s='0.83'/>
      <point id='BL5' x='1298' y='450' s='0.90'/>
      <point id='BL6' x='1226' y='437' s='0.85'/>
      <point id='N3' x='1068' y='737' s='0.92'/>
      <point id='M9' x='1068' y='834' s='0.83'/>
      <point id='F3' x='779' y='558' s='0.87'/>
      <point id='F9' x='1346' y='535' s='0.95'/>
      <point id='F4' x='817' y='741' s='0.81'/>
      <point id='F8' x='1323' y='720' s='0.90'/>
      <point id='F6' x='1041' y='1041' s='0.66'/>
      <point id='F5' x='849' y='880' s='0.78'/>
      <point id='F7' x='1307' y='879' s='0.83'/>
    </features>
  </face>
</faces>

自分の使いたい用途に合わせるとしたら、どこか口以外の基準点から標準化するポイントを複数見つけておいて、画像のサイズを標準化してから、口の周りの特徴点の距離を調べるという方法だろうか。

AWS Rekognito

画像認識に関連したいろいろなサービスがある感じ。
これが人工知能というくくりのサービスなのはちょっと変な感じするが。
f:id:benzenetarou:20171125235645p:plain
デモページもあった。
f:id:benzenetarou:20171125235803p:plain
ただ、これを見ると、でもページの画像でさえ口開いてるのに閉じてるって判定されてたので(しかも信頼度72%)、精度はそこまで良くないのかもしれない。
試しに自分で取った写真をアップロードしてみても、ちょくちょく誤判定されてしまっている。

JSONも返してくれるが、特徴量が少ない気がする。特に口周り。

{
    "FaceDetails": [
        {
            "BoundingBox": {
                "Width": 0.2618750035762787,
                "Height": 0.3930581510066986,
                "Left": 0.14937500655651093,
                "Top": 0.13414634764194489
            },
            "AgeRange": {
                "Low": 26,
                "High": 43
            },
            "Smile": {
                "Value": true,
                "Confidence": 99.0987319946289
            },
            "Eyeglasses": {
                "Value": true,
                "Confidence": 99.99999237060547
            },
            "Sunglasses": {
                "Value": true,
                "Confidence": 95.7325439453125
            },
            "Gender": {
                "Value": "Female",
                "Confidence": 100
            },
            "Beard": {
                "Value": false,
                "Confidence": 99.90460205078125
            },
            "Mustache": {
                "Value": false,
                "Confidence": 92.63785552978516
            },
            "EyesOpen": {
                "Value": true,
                "Confidence": 85.72091674804688
            },
            "MouthOpen": {
                "Value": false,
                "Confidence": 72.2423095703125
            },
            "Emotions": [
                {
                    "Type": "HAPPY",
                    "Confidence": 99.69184112548828
                },
                {
                    "Type": "CALM",
                    "Confidence": 1.0618865489959717
                },
                {
                    "Type": "ANGRY",
                    "Confidence": 0.44966936111450195
                }
            ],
            "Landmarks": [
                {
                    "Type": "eyeLeft",
                    "X": 0.2459116131067276,
                    "Y": 0.2963947355747223
                },
                {
                    "Type": "eyeRight",
                    "X": 0.32498690485954285,
                    "Y": 0.28320804238319397
                },
                {
                    "Type": "nose",
                    "X": 0.2972606122493744,
                    "Y": 0.338502436876297
                },
                {
                    "Type": "mouthLeft",
                    "X": 0.24349376559257507,
                    "Y": 0.43834927678108215
                },
                {
                    "Type": "mouthRight",
                    "X": 0.32762306928634644,
                    "Y": 0.4251043200492859
                },
                {
                    "Type": "leftPupil",
                    "X": 0.2514769434928894,
                    "Y": 0.2966562509536743
                },
                {
                    "Type": "rightPupil",
                    "X": 0.3339778780937195,
                    "Y": 0.28375154733657837
                },
                {
                    "Type": "leftEyeBrowLeft",
                    "X": 0.2145199030637741,
                    "Y": 0.2463952600955963
                },
                {
                    "Type": "leftEyeBrowUp",
                    "X": 0.2372017651796341,
                    "Y": 0.22838076949119568
                },
                {
                    "Type": "leftEyeBrowRight",
                    "X": 0.26641586422920227,
                    "Y": 0.2389357089996338
                },
                {
                    "Type": "rightEyeBrowLeft",
                    "X": 0.2992517352104187,
                    "Y": 0.23876728117465973
                },
                {
                    "Type": "rightEyeBrowUp",
                    "X": 0.3239707052707672,
                    "Y": 0.22483669221401215
                },
                {
                    "Type": "rightEyeBrowRight",
                    "X": 0.3491824269294739,
                    "Y": 0.23105363547801971
                },
                {
                    "Type": "leftEyeLeft",
                    "X": 0.22946621477603912,
                    "Y": 0.3019390404224396
                },
                {
                    "Type": "leftEyeRight",
                    "X": 0.2616650462150574,
                    "Y": 0.29498758912086487
                },
                {
                    "Type": "leftEyeUp",
                    "X": 0.24515913426876068,
                    "Y": 0.2873624563217163
                },
                {
                    "Type": "leftEyeDown",
                    "X": 0.24701011180877686,
                    "Y": 0.3033584654331207
                },
                {
                    "Type": "rightEyeLeft",
                    "X": 0.30755841732025146,
                    "Y": 0.2871529757976532
                },
                {
                    "Type": "rightEyeRight",
                    "X": 0.3417114317417145,
                    "Y": 0.2818377912044525
                },
                {
                    "Type": "rightEyeUp",
                    "X": 0.3245350122451782,
                    "Y": 0.27441540360450745
                },
                {
                    "Type": "rightEyeDown",
                    "X": 0.3257909119129181,
                    "Y": 0.2907133102416992
                },
                {
                    "Type": "noseLeft",
                    "X": 0.2787094712257385,
                    "Y": 0.38133224844932556
                },
                {
                    "Type": "noseRight",
                    "X": 0.308928519487381,
                    "Y": 0.37534570693969727
                },
                {
                    "Type": "mouthUp",
                    "X": 0.2901398241519928,
                    "Y": 0.4091244339942932
                },
                {
                    "Type": "mouthDown",
                    "X": 0.29399216175079346,
                    "Y": 0.4643388092517853
                }
            ],
            "Pose": {
                "Roll": -8.045867919921875,
                "Yaw": 17.916576385498047,
                "Pitch": 13.156755447387695
            },
            "Quality": {
                "Brightness": 40.16537857055664,
                "Sharpness": 99.9980239868164
            },
            "Confidence": 99.82343292236328
        }
    ]
}

Google Cloud Vision

顔認識もできるし、いろいろな情報も併せてくれる。
f:id:benzenetarou:20171126002419p:plain こんな感じでJSONを返してくれたりもするみたいだ。

dandaIMGL5048_TP_V.jpg
 {
  "faceAnnotations": [
    {
      "boundingPoly": {
        "vertices": [
          {
            "x": 583,
            "y": 49
          },
          {
            "x": 1029,
            "y": 49
          },
          {
            "x": 1029,
            "y": 567
          },
          {
            "x": 583,
            "y": 567
          }
        ]
      },
      "fdBoundingPoly": {
        "vertices": [
          {
            "x": 649,
            "y": 161
          },
          {
            "x": 975,
            "y": 161
          },
          {
            "x": 975,
            "y": 487
          },
          {
            "x": 649,
            "y": 487
          }
        ]
      },
      "landmarks": [
        {
          "type": "LEFT_EYE",
          "position": {
            "x": 719.5262,
            "y": 253.72295,
            "z": 0.000031506435
          }
        },
        {
          "type": "RIGHT_EYE",
          "position": {
            "x": 849.03937,
            "y": 267.75647,
            "z": -36.321598
          }
        },
        {
          "type": "LEFT_OF_LEFT_EYEBROW",
          "position": {
            "x": 686.8778,
            "y": 221.65237,
            "z": 27.712809
          }
        },
        {
          "type": "RIGHT_OF_LEFT_EYEBROW",
          "position": {
            "x": 754.03094,
            "y": 225.36835,
            "z": -30.87542
          }
        },
        {
          "type": "LEFT_OF_RIGHT_EYEBROW",
          "position": {
            "x": 816.0152,
            "y": 231.25845,
            "z": -48.467567
          }
        },
        {
          "type": "RIGHT_OF_RIGHT_EYEBROW",
          "position": {
            "x": 902.0392,
            "y": 247.53323,
            "z": -33.053932
          }
        },
        {
          "type": "MIDPOINT_BETWEEN_EYES",
          "position": {
            "x": 780.317,
            "y": 254.34734,
            "z": -45.376167
          }
        },
        {
          "type": "NOSE_TIP",
          "position": {
            "x": 760.82666,
            "y": 325.50342,
            "z": -93.64924
          }
        },
        {
          "type": "UPPER_LIP",
          "position": {
            "x": 758.0111,
            "y": 379.47757,
            "z": -70.47521
          }
        },
        {
          "type": "LOWER_LIP",
          "position": {
            "x": 750.76904,
            "y": 417.12973,
            "z": -67.296265
          }
        },
        {
          "type": "MOUTH_LEFT",
          "position": {
            "x": 709.657,
            "y": 398.31903,
            "z": -23.435837
          }
        },
        {
          "type": "MOUTH_RIGHT",
          "position": {
            "x": 818.4644,
            "y": 414.50378,
            "z": -52.383175
          }
        },
        {
          "type": "MOUTH_CENTER",
          "position": {
            "x": 756.7259,
            "y": 397.63107,
            "z": -63.729393
          }
        },
        {
          "type": "NOSE_BOTTOM_RIGHT",
          "position": {
            "x": 805.1426,
            "y": 346.799,
            "z": -55.52658
          }
        },
        {
          "type": "NOSE_BOTTOM_LEFT",
          "position": {
            "x": 733.29297,
            "y": 340.7746,
            "z": -35.59469
          }
        },
        {
          "type": "NOSE_BOTTOM_CENTER",
          "position": {
            "x": 763.22516,
            "y": 350.47217,
            "z": -67.04768
          }
        },
        {
          "type": "LEFT_EYE_TOP_BOUNDARY",
          "position": {
            "x": 721.1245,
            "y": 244.94334,
            "z": -8.15667
          }
        },
        {
          "type": "LEFT_EYE_RIGHT_CORNER",
          "position": {
            "x": 747.6377,
            "y": 259.79794,
            "z": -7.71021
          }
        },
        {
          "type": "LEFT_EYE_BOTTOM_BOUNDARY",
          "position": {
            "x": 718.8527,
            "y": 263.2192,
            "z": -2.942062
          }
        },
        {
          "type": "LEFT_EYE_LEFT_CORNER",
          "position": {
            "x": 699.73505,
            "y": 254.79391,
            "z": 18.149515
          }
        },
        {
          "type": "LEFT_EYE_PUPIL",
          "position": {
            "x": 719.1574,
            "y": 254.79868,
            "z": -3.427162
          }
        },
        {
          "type": "RIGHT_EYE_TOP_BOUNDARY",
          "position": {
            "x": 849.89404,
            "y": 260.44193,
            "z": -44.4995
          }
        },
        {
          "type": "RIGHT_EYE_RIGHT_CORNER",
          "position": {
            "x": 883.94965,
            "y": 276.54755,
            "z": -32.000946
          }
        },
        {
          "type": "RIGHT_EYE_BOTTOM_BOUNDARY",
          "position": {
            "x": 849.20135,
            "y": 277.8363,
            "z": -39.35131
          }
        },
        {
          "type": "RIGHT_EYE_LEFT_CORNER",
          "position": {
            "x": 823.90643,
            "y": 269.1588,
            "z": -29.444563
          }
        },
        {
          "type": "RIGHT_EYE_PUPIL",
          "position": {
            "x": 851.3009,
            "y": 270.67163,
            "z": -40.867477
          }
        },
        {
          "type": "LEFT_EYEBROW_UPPER_MIDPOINT",
          "position": {
            "x": 720.2593,
            "y": 205.07675,
            "z": -7.762258
          }
        },
        {
          "type": "RIGHT_EYEBROW_UPPER_MIDPOINT",
          "position": {
            "x": 859.88257,
            "y": 221.919,
            "z": -47.19713
          }
        },
        {
          "type": "LEFT_EAR_TRAGION",
          "position": {
            "x": 670.9434,
            "y": 339.6975,
            "z": 171.91898
          }
        },
        {
          "type": "RIGHT_EAR_TRAGION",
          "position": {
            "x": 963.58167,
            "y": 374.80206,
            "z": 89.242195
          }
        },
        {
          "type": "FOREHEAD_GLABELLA",
          "position": {
            "x": 783.9658,
            "y": 224.99454,
            "z": -44.277527
          }
        },
        {
          "type": "CHIN_GNATHION",
          "position": {
            "x": 748.6527,
            "y": 486.20532,
            "z": -57.60541
          }
        },
        {
          "type": "CHIN_LEFT_GONION",
          "position": {
            "x": 658.22473,
            "y": 415.8591,
            "z": 103.73505
          }
        },
        {
          "type": "CHIN_RIGHT_GONION",
          "position": {
            "x": 922.45325,
            "y": 447.898,
            "z": 29.103714
          }
        }
      ],
      "rollAngle": 9.122541,
      "panAngle": -15.808093,
      "tiltAngle": 7.937726,
      "detectionConfidence": 0.8733366,
      "landmarkingConfidence": 0.5498749,
      "joyLikelihood": "VERY_UNLIKELY",
      "sorrowLikelihood": "VERY_UNLIKELY",
      "angerLikelihood": "VERY_UNLIKELY",
      "surpriseLikelihood": "VERY_UNLIKELY",
      "underExposedLikelihood": "VERY_UNLIKELY",
      "blurredLikelihood": "VERY_UNLIKELY",
      "headwearLikelihood": "VERY_UNLIKELY"
    }
  ],
  "labelAnnotations": [
    {
      "mid": "/m/01xyhv",
      "description": "suit",
      "score": 0.855479
    },
    {
      "mid": "/m/01qkbx",
      "description": "professional",
      "score": 0.84650135
    },
    {
      "mid": "/m/0n5szg6",
      "description": "business executive",
      "score": 0.81015223
    },
    {
      "mid": "/m/012t_z",
      "description": "businessperson",
      "score": 0.7535335
    },
    {
      "mid": "/m/01kq3x",
      "description": "white collar worker",
      "score": 0.67039
    },
    {
      "mid": "/m/0289fz",
      "description": "executive officer",
      "score": 0.6614492
    },
    {
      "mid": "/m/09x_r",
      "description": "entrepreneur",
      "score": 0.638664
    },
    {
      "mid": "/m/09s1f",
      "description": "business",
      "score": 0.5803496
    },
    {
      "mid": "/m/05s9tm",
      "description": "talent manager",
      "score": 0.51430815
    },
    {
      "mid": "/m/019p5q",
      "description": "gentleman",
      "score": 0.50934017
    }
  ],
  "safeSearchAnnotation": {
    "adult": "VERY_UNLIKELY",
    "spoof": "VERY_UNLIKELY",
    "medical": "VERY_UNLIKELY",
    "violence": "VERY_UNLIKELY"
  },
  "imagePropertiesAnnotation": {
    "dominantColors": {
      "colors": [
        {
          "color": {
            "red": 230,
            "green": 230,
            "blue": 237
          },
          "score": 0.5333071,
          "pixelFraction": 0.5140667
        },
        {
          "color": {
            "red": 26,
            "green": 26,
            "blue": 28
          },
          "score": 0.21879509,
          "pixelFraction": 0.26526666
        },
        {
          "color": {
            "red": 205,
            "green": 203,
            "blue": 213
          },
          "score": 0.1390053,
          "pixelFraction": 0.11186667
        },
        {
          "color": {
            "red": 45,
            "green": 43,
            "blue": 47
          },
          "score": 0.029963521,
          "pixelFraction": 0.0282
        },
        {
          "color": {
            "red": 225,
            "green": 190,
            "blue": 189
          },
          "score": 0.01627379,
          "pixelFraction": 0.012933333
        },
        {
          "color": {
            "red": 164,
            "green": 159,
            "blue": 165
          },
          "score": 0.014597795,
          "pixelFraction": 0.0132
        },
        {
          "color": {
            "red": 226,
            "green": 183,
            "blue": 174
          },
          "score": 0.007874987,
          "pixelFraction": 0.014666666
        },
        {
          "color": {
            "red": 123,
            "green": 118,
            "blue": 121
          },
          "score": 0.0060422462,
          "pixelFraction": 0.0058
        },
        {
          "color": {
            "red": 246,
            "green": 214,
            "blue": 214
          },
          "score": 0.005779971,
          "pixelFraction": 0.0045333332
        },
        {
          "color": {
            "red": 86,
            "green": 83,
            "blue": 85
          },
          "score": 0.005673399,
          "pixelFraction": 0.0055333334
        }
      ]
    }
  },
  "cropHintsAnnotation": {
    "cropHints": [
      {
        "boundingPoly": {
          "vertices": [
            {
              "x": 351
            },
            {
              "x": 1215
            },
            {
              "x": 1215,
              "y": 1065
            },
            {
              "x": 351,
              "y": 1065
            }
          ]
        },
        "confidence": 1,
        "importanceFraction": 0.95
      },
      {
        "boundingPoly": {
          "vertices": [
            {
              "x": 255
            },
            {
              "x": 1343
            },
            {
              "x": 1343,
              "y": 1065
            },
            {
              "x": 255,
              "y": 1065
            }
          ]
        },
        "confidence": 1,
        "importanceFraction": 0.98999995
      },
      {
        "boundingPoly": {
          "vertices": [
            {
              "x": 127
            },
            {
              "x": 1423
            },
            {
              "x": 1423,
              "y": 1065
            },
            {
              "x": 127,
              "y": 1065
            }
          ]
        },
        "confidence": 1,
        "importanceFraction": 0.98999995
      }
    ]
  },
  "webDetection": {
    "webEntities": [
      {
        "entityId": "/g/12bprj7th",
        "score": 1.1322
      },
      {
        "entityId": "/g/1tgwpw14",
        "score": 1.0244
      },
      {
        "entityId": "/m/09s1f",
        "score": 0.7011,
        "description": "Business"
      },
      {
        "entityId": "/m/01m2bt",
        "score": 0.6569,
        "description": "Venture capital"
      },
      {
        "entityId": "/m/03bxgrp",
        "score": 0.6054,
        "description": "Company"
      },
      {
        "entityId": "/m/018s5w",
        "score": 0.5943,
        "description": "Capital"
      },
      {
        "entityId": "/m/02_7t",
        "score": 0.5428,
        "description": "Finance"
      },
      {
        "entityId": "/g/1203l_bt9",
        "score": 0.5194
      },
      {
        "entityId": "/m/02nwq",
        "score": 0.5148,
        "description": "Entrepreneurship"
      },
      {
        "entityId": "/m/03m3ym9",
        "score": 0.5101,
        "description": "Angel investor"
      },
      {
        "entityId": "/m/023k2",
        "score": 0.4496,
        "description": "Corporation"
      },
      {
        "entityId": "/m/03jzl9",
        "score": 0.4287,
        "description": "Share"
      },
      {
        "entityId": "/m/0dlrgy",
        "score": 0.4206,
        "description": "All China Lawyers Association"
      },
      {
        "entityId": "/m/0h3z7",
        "score": 0.4193,
        "description": "Initial public offering"
      },
      {
        "entityId": "/m/0lbmv",
        "score": 0.20668001,
        "description": "Shenzhen"
      }
    ],
    "fullMatchingImages": [
      {
        "url": "http://timscholten.com/wp-content/uploads/2017/05/TimScholten-5.jpg"
      },
      {
        "url": "https://www.pakutaso.com/shared/img/thumb/dandaIMGL5048.jpg"
      },
      {
        "url": "http://stat.profile.ameba.jp/profile_images/20130317/19/1e/9f/j/o180025201363516460689.jpg"
      },
      {
        "url": "http://www.gdszlvshi.com/UploadFiles/201662015263786.jpg"
      },
      {
        "url": "https://www.pakutaso.com/shared/img/thumb/dandaIMGL5048_TP_V.jpg"
      },
      {
        "url": "https://image.jimcdn.com/app/cms/image/transf/none/path/s4684c34cfcc8a80d/image/i400bb0104d9c3726/version/1454214695/image.jpg"
      },
      {
        "url": "http://felvidek.ma/wp-content/uploads/2017/10/Becse-Norbert-fot%C3%B3-MKP.jpg"
      },
      {
        "url": "http://www.wscom.com.br/arqs/arquivos/arquivos/201303081003240000008330.jpg"
      },
      {
        "url": "http://upload-images.jianshu.io/upload_images/2548788-875d2c3997d23c47.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"
      },
      {
        "url": "https://www.pakutaso.com/shared/img/thumb/dandaIMGL5048_TP_V4.jpg"
      },
      {
        "url": "http://www.prachachat.net/online/2016/10/14775681281477568341l.jpg"
      },
      {
        "url": "http://www.yemacaijing.com/kindeditor/attached/image/20160212/20160212155959_73709.jpg"
      },
      {
        "url": "https://www.pakutaso.com/shared/img/thumb/dandaIMGL5048_TP_V1.jpg"
      },
      {
        "url": "http://stat.profile.ameba.jp/profile_images/20160930/02/f3/Ot/j/o045204531475169331139.jpg"
      },
      {
        "url": "https://image.jimcdn.com/app/cms/image/transf/dimension=210x1024:format=jpg/path/s4684c34cfcc8a80d/image/i400bb0104d9c3726/version/1454214695/image.jpg"
      },
      {
        "url": "http://happymakeproject.com/wp-content/uploads/2016/02/image.jpg"
      },
      {
        "url": "https://media.licdn.com/mpr/mpr/shrinknp_200_200/AAEAAQAAAAAAAATyAAAAJDEyMmM3MWY3LTQwZmYtNGI1Yi05ZjA1LTIxNzJhOGM1NjU4Ng.jpg"
      },
      {
        "url": "https://media.licdn.com/mpr/mpr/shrinknp_200_200/AAEAAQAAAAAAAA3VAAAAJGY0ZThlMDg3LTdlMTYtNDAxMC05NzZhLTJiYWRmNjA5OTM0Yw.jpg"
      },
      {
        "url": "http://img2.cyzone.cn/uploadfile/2015/0710/fa4005041f412e73d576693097584382.jpg"
      },
      {
        "url": "https://media.licdn.com/mpr/mpr/shrink_100_100/AAEAAQAAAAAAAAxMAAAAJDliODg1ZWJmLTM5OTctNDk1Yi1iMTg0LTc5MWQ4YjAxZGQ3Yw.jpg"
      }
    ],
    "partialMatchingImages": [
      {
        "url": "https://www.pakutaso.com/shared/img/thumb/dandaIMGL5048_TP_V2.jpg"
      }
    ],
    "pagesWithMatchingImages": [
      {
        "url": "https://www.linkedin.com/in/andrewbwillis"
      },
      {
        "url": "http://timscholten.com/media-sheet/images/"
      },
      {
        "url": "https://sg.linkedin.com/in/amy-aw-1804433a"
      },
      {
        "url": "https://holaconnect.com/profile/margaux-lefort-email-phone-93fee623"
      },
      {
        "url": "http://felvidek.ma/2017/10/becse-norbert-mindent-megtesz-a-komaromi-jaras-fellenditese-erdekeben/"
      },
      {
        "url": "http://www.yemacaijing.com/index/view/id/84.html"
      },
      {
        "url": "http://www.yemacaijing.com/Index/view/id/84.html"
      },
      {
        "url": "http://www.wscom.com.br/noticias/economia/JOAO+PESSOA+GANHA+CONCESSIONARIA+AUDI+-145150"
      },
      {
        "url": "https://www.pakutaso.com/person/"
      },
      {
        "url": "http://www.cyzone.cn/d/20150601/470.html"
      },
      {
        "url": "http://happymakeproject.com/8678/"
      },
      {
        "url": "https://dandashokai.com/8667"
      },
      {
        "url": "http://www.cyzone.cn/f/20150710/1664.html"
      },
      {
        "url": "https://www.prachachat.net/news_detail.php?newsid=1477568128"
      },
      {
        "url": "https://www.pakutaso.com/person/man/"
      },
      {
        "url": "http://www.jianshu.com/p/3580429d0a50"
      },
      {
        "url": "http://m.prachachat.net/news_detail.php?newsid=1477568128"
      },
      {
        "url": "http://profile.ameba.jp/dramamaster/"
      },
      {
        "url": "http://profile.ameba.jp/panicgekitai"
      },
      {
        "url": "http://www.gdszlvshi.com/showteam_1.html"
      },
      {
        "url": "http://www.gdszlvshi.com/team.html"
      },
      {
        "url": "https://panickaiketsu.jimdo.com/%E4%B8%89%E6%9C%A8%E3%83%92%E3%83%AD%E3%82%B7%E3%81%AE%E3%83%97%E3%83%AD%E3%83%95%E3%82%A3%E3%83%BC%E3%83%AB/"
      },
      {
        "url": "http://hyperacy.site/%E0%B9%82%E0%B8%A1%E0%B8%A1%E0%B8%B9%E0%B8%AB%E0%B8%B0-%E0%B8%A3%E0%B8%B2%E0%B8%84%E0%B8%B2/"
      },
      {
        "url": "http://www.10800.com/investor/details/id/14309"
      },
      {
        "url": "https://holaconnect.com/profile/maman-bureau-email-phone-acf2ca62"
      }
    ],
    "visuallySimilarImages": [
      {
        "url": "http://www.realestateforachangingworld.co.uk/wp-content/uploads/2017/10/andy_martin-500x500.jpg"
      },
      {
        "url": "https://www.irep.co.jp/global/wp-content/uploads/2016/03/Samuel3-e1485150192823.png"
      },
      {
        "url": "http://www.bu.ac.th/wp-content/uploads/2016/07/%E0%B8%84%E0%B8%B8%E0%B8%93%E0%B8%A8%E0%B8%B8%E0%B8%A0%E0%B8%8A%E0%B8%B1%E0%B8%A2.jpg"
      },
      {
        "url": "https://ireward.superghs.com/resource/htoo/page/Mr-Manish-Gupta.jpg"
      },
      {
        "url": "http://www.recruit-rgf.com/csr/img/csr_common_president01.jpg"
      },
      {
        "url": "https://www.capitalfirst.com/front-end/img/board-members/bm2.jpg"
      },
      {
        "url": "https://www.dialog.lk/dialogdocroot/content/images/pr/supun.jpg"
      },
      {
        "url": "https://lookaside.fbsbx.com/lookaside/crawler/media/?media_id=1116763798456374"
      },
      {
        "url": "https://8iholdings.com/wp-content/uploads/2017/02/Profile-Joshua-440x440.jpg"
      }
    ]
  }
}

Microsoft Face API

返ってくるJSONはこんな感じらしい。

Response:
[
  {
    "faceAttributes": {
      "accessories": [],
      "age": 22.9,
      "blur": {
        "blurLevel": "low",
        "value": 0.06
      },
      "emotion": {
        "anger": 0.0,
        "contempt": 0.0,
        "disgust": 0.0,
        "fear": 0.0,
        "happiness": 0.0,
        "neutral": 0.986,
        "sadness": 0.009,
        "surprise": 0.005
      },
      "exposure": {
        "exposureLevel": "goodExposure",
        "value": 0.67
      },
      "facialHair": {
        "beard": 0.0,
        "moustache": 0.0,
        "sideburns": 0.0
      },
      "gender": "female",
      "glasses": "NoGlasses",
      "hair": {
        "bald": 0.0,
        "hairColor": [
          {
            "color": "brown",
            "confidence": 1.0
          },
          {
            "color": "black",
            "confidence": 0.87
          },
          {
            "color": "other",
            "confidence": 0.51
          },
          {
            "color": "blond",
            "confidence": 0.08
          },
          {
            "color": "red",
            "confidence": 0.08
          },
          {
            "color": "gray",
            "confidence": 0.02
          }
        ],
        "invisible": false
      },
      "headPose": {
        "pitch": 0.0,
        "roll": 0.1,
        "yaw": -32.9
      },
      "makeup": {
        "eyeMakeup": true,
        "lipMakeup": true
      },
      "noise": {
        "noiseLevel": "low",
        "value": 0.0
      },
      "occlusion": {
        "eyeOccluded": false,
        "foreheadOccluded": false,
        "mouthOccluded": false
      },
      "smile": 0.0
    },
    "faceId": "49d55c17-e018-4a42-ba7b-8cbbdfae7c6f",
    "faceRectangle": {
      "height": 162,
      "left": 177,
      "top": 131,
      "width": 162
    }
  }
]

あまり詳細な表情の情報は渡してくれないみたいだ。
もうこっちがやっとくから、君たちは結果だけ見てればいいの。感が強い。ヤダ。

Face++

Face Landmark SDK - Face++ Cognitive Services

なんと顔の特徴料が106個あると。
しかも無料だと。
なんやかんやこれが一番良さそうだ。

ただ、使ってみるまでがちょっと面倒くさいです。
まずアカウントを登録して(メールが届くのが5分くらいかかった!)、携帯電話番号とかも登録しなくちゃAPIKeyが取得できません。
これで使い物にならなかったら腹立ちますね。 中国のサービスなので、電話番号とか悪用されないかなと心配してたのですが、この会社割りと有名な会社だったみたいですね。恥ずかしい。

中国の顔認証スタートアップFace++が2500万米ドルを調達、Jack Ma(馬雲)氏も顔認証決済を導入へ - THE BRIDGE(ザ・ブリッジ)

{
  "image_id": "5XkklHlGoc1WTKaw25w5ww==",
  "request_id": "1511624338,e08986b0-891b-46fb-9b1a-2c77a83dccbb",
  "time_used": 427,
  "faces": [
    {
      "landmark": {
        "mouth_upper_lip_left_contour2": {
          "y": 384,
          "x": 719
        },
        "mouth_upper_lip_left_contour3": {
          "y": 396,
          "x": 728
        },
        "mouth_lower_lip_right_contour3": {
          "y": 415,
          "x": 772
        },
        "mouth_upper_lip_left_contour1": {
          "y": 372,
          "x": 743
        },
        "left_eye_upper_left_quarter": {
          "y": 248,
          "x": 706
        },
        "left_eyebrow_lower_middle": {
          "y": 220,
          "x": 716
        },
        "contour_chin": {
          "y": 511,
          "x": 750
        },
        "left_eyebrow_lower_left_quarter": {
          "y": 219,
          "x": 699
        },
        "right_eyebrow_lower_left_quarter": {
          "y": 235,
          "x": 832
        },
        "mouth_lower_lip_right_contour1": {
          "y": 402,
          "x": 783
        },
        "mouth_lower_lip_left_contour2": {
          "y": 408,
          "x": 717
        },
        "left_eye_bottom": {
          "y": 260,
          "x": 717
        },
        "mouth_lower_lip_bottom": {
          "y": 412,
          "x": 753
        },
        "contour_left9": {
          "y": 497,
          "x": 717
        },
        "mouth_lower_lip_top": {
          "y": 394,
          "x": 755
        },
        "right_eyebrow_upper_middle": {
          "y": 217,
          "x": 859
        },
        "right_eyebrow_left_corner": {
          "y": 232,
          "x": 806
        },
        "right_eye_bottom": {
          "y": 279,
          "x": 849
        },
        "contour_left7": {
          "y": 448,
          "x": 680
        },
        "contour_left6": {
          "y": 416,
          "x": 673
        },
        "contour_left5": {
          "y": 382,
          "x": 671
        },
        "contour_left4": {
          "y": 349,
          "x": 671
        },
        "contour_left3": {
          "y": 317,
          "x": 672
        },
        "contour_left2": {
          "y": 287,
          "x": 677
        },
        "contour_left1": {
          "y": 257,
          "x": 684
        },
        "left_eye_lower_left_quarter": {
          "y": 258,
          "x": 705
        },
        "mouth_upper_lip_top": {
          "y": 376,
          "x": 757
        },
        "contour_right3": {
          "y": 371,
          "x": 992
        },
        "contour_right2": {
          "y": 328,
          "x": 997
        },
        "mouth_left_corner": {
          "y": 405,
          "x": 701
        },
        "contour_right4": {
          "y": 414,
          "x": 982
        },
        "contour_right7": {
          "y": 504,
          "x": 893
        },
        "left_eyebrow_left_corner": {
          "y": 219,
          "x": 683
        },
        "nose_right": {
          "y": 346,
          "x": 814
        },
        "right_eye_upper_right_quarter": {
          "y": 266,
          "x": 867
        },
        "nose_tip": {
          "y": 321,
          "x": 757
        },
        "contour_right5": {
          "y": 453,
          "x": 963
        },
        "nose_contour_lower_middle": {
          "y": 354,
          "x": 762
        },
        "right_eye_top": {
          "y": 261,
          "x": 850
        },
        "mouth_lower_lip_left_contour3": {
          "y": 410,
          "x": 735
        },
        "right_eye_right_corner": {
          "y": 275,
          "x": 881
        },
        "right_eye_lower_right_quarter": {
          "y": 278,
          "x": 866
        },
        "mouth_upper_lip_right_contour2": {
          "y": 393,
          "x": 794
        },
        "right_eyebrow_lower_right_quarter": {
          "y": 239,
          "x": 883
        },
        "left_eye_left_corner": {
          "y": 253,
          "x": 695
        },
        "mouth_right_corner": {
          "y": 417,
          "x": 811
        },
        "mouth_upper_lip_right_contour3": {
          "y": 402,
          "x": 784
        },
        "right_eye_lower_left_quarter": {
          "y": 276,
          "x": 834
        },
        "left_eyebrow_right_corner": {
          "y": 224,
          "x": 752
        },
        "left_eyebrow_lower_right_quarter": {
          "y": 223,
          "x": 734
        },
        "right_eye_center": {
          "y": 272,
          "x": 850
        },
        "left_eye_upper_right_quarter": {
          "y": 251,
          "x": 731
        },
        "mouth_lower_lip_left_contour1": {
          "y": 396,
          "x": 728
        },
        "contour_left8": {
          "y": 475,
          "x": 695
        },
        "nose_left": {
          "y": 336,
          "x": 726
        },
        "right_eyebrow_lower_middle": {
          "y": 236,
          "x": 858
        },
        "left_eye_top": {
          "y": 247,
          "x": 718
        },
        "left_eye_center": {
          "y": 255,
          "x": 718
        },
        "left_eye_lower_right_quarter": {
          "y": 260,
          "x": 729
        },
        "nose_contour_right1": {
          "y": 269,
          "x": 805
        },
        "contour_right9": {
          "y": 516,
          "x": 798
        },
        "right_eye_left_corner": {
          "y": 272,
          "x": 819
        },
        "left_eyebrow_upper_left_quarter": {
          "y": 207,
          "x": 698
        },
        "left_eye_pupil": {
          "y": 253,
          "x": 718
        },
        "right_eyebrow_upper_left_quarter": {
          "y": 219,
          "x": 831
        },
        "contour_right8": {
          "y": 514,
          "x": 846
        },
        "right_eyebrow_right_corner": {
          "y": 243,
          "x": 906
        },
        "right_eye_upper_left_quarter": {
          "y": 264,
          "x": 833
        },
        "left_eyebrow_upper_middle": {
          "y": 206,
          "x": 718
        },
        "right_eyebrow_upper_right_quarter": {
          "y": 223,
          "x": 886
        },
        "nose_contour_left1": {
          "y": 265,
          "x": 758
        },
        "nose_contour_left2": {
          "y": 312,
          "x": 738
        },
        "mouth_upper_lip_right_contour1": {
          "y": 375,
          "x": 771
        },
        "contour_right1": {
          "y": 285,
          "x": 999
        },
        "nose_contour_right2": {
          "y": 320,
          "x": 806
        },
        "mouth_lower_lip_right_contour2": {
          "y": 417,
          "x": 792
        },
        "contour_right6": {
          "y": 484,
          "x": 933
        },
        "nose_contour_right3": {
          "y": 352,
          "x": 788
        },
        "nose_contour_left3": {
          "y": 346,
          "x": 742
        },
        "left_eye_right_corner": {
          "y": 258,
          "x": 740
        },
        "left_eyebrow_upper_right_quarter": {
          "y": 211,
          "x": 737
        },
        "right_eye_pupil": {
          "y": 269,
          "x": 849
        },
        "mouth_upper_lip_bottom": {
          "y": 395,
          "x": 755
        }
      },
      "attributes": {
        "emotion": {
          "sadness": 0.044,
          "neutral": 31.453,
          "disgust": 21.391,
          "anger": 47.017,
          "surprise": 0.032,
          "fear": 0.032,
          "happiness": 0.032
        },
        "gender": {
          "value": "Male"
        },
        "age": {
          "value": 37
        },
        "eyestatus": {
          "left_eye_status": {
            "normal_glass_eye_open": 1.785,
            "no_glass_eye_close": 0,
            "occlusion": 0.355,
            "no_glass_eye_open": 97.712,
            "normal_glass_eye_close": 0.003,
            "dark_glasses": 0.145
          },
          "right_eye_status": {
            "normal_glass_eye_open": 0.004,
            "no_glass_eye_close": 0,
            "occlusion": 0.005,
            "no_glass_eye_open": 99.991,
            "normal_glass_eye_close": 0,
            "dark_glasses": 0
          }
        },
        "glass": {
          "value": "None"
        },
        "headpose": {
          "yaw_angle": 22.35016,
          "pitch_angle": -8.229241,
          "roll_angle": 9.0831785
        },
        "blur": {
          "blurness": {
            "threshold": 50,
            "value": 0.378
          },
          "motionblur": {
            "threshold": 50,
            "value": 0.378
          },
          "gaussianblur": {
            "threshold": 50,
            "value": 0.378
          }
        },
        "smile": {
          "threshold": 30.1,
          "value": 3.299
        },
        "facequality": {
          "threshold": 70.1,
          "value": 6.392
        },
        "ethnicity": {
          "value": "Asian"
        }
      },
      "face_rectangle": {
        "width": 316,
        "top": 201,
        "left": 657,
        "height": 316
      },
      "face_token": "06ae41e4d9d9fe3f614ea82bfbdeb289"
    }
  ]

まずはDetect APIで顔を検出してから、その顔のIDを渡すことでAnalyze APIが使えるようです。

IBM

デモページはこんな感じ
f:id:benzenetarou:20171127231201p:plain
Sweetheart(恋人)かどうか判定できるってのはすごいな〜
男女二人組を恋人なのかどうか見分けられるってすごいな〜

でも特徴量とかはくれないみたいだ。
JSONはこんな感じ。

結果とスコアしかくれない。

{
  "classes": [
    {
      "class": "person",
      "score": 0.851
    },
    {
      "class": "sweetheart",
      "score": 0.581,
      "type_hierarchy": "/person/sweetheart"
    },
    {
      "class": "people",
      "score": 0.597,
      "type_hierarchy": "/person/people"
    },
    {
      "class": "couple",
      "score": 0.518,
      "type_hierarchy": "/person/couple"
    },
    {
      "class": "woman",
      "score": 0.556,
      "type_hierarchy": "/person/female/woman"
    },
    {
      "class": "female",
      "score": 0.5
    },
    {
      "class": "Indian red color",
      "score": 0.988
    }
  ],
  "classifier_id": "default",
  "name": "default"
}

結論

結論は、Face++を使ってみることにしました。
理由としては、特徴量が一番多く、精度も高そうだったためです。
(と、まとめるにあたってもう一度見渡してみたら、無料だしdetectFaceでも良かったのではないかな…と思っています。
と思って改めて見てみたら2018/0404にサービス終了していました。そういえば、これを採用しなかった理由は、提供元があまり聞いたことない会社だったのとで、いつサービス終了するかわからなかったのと、http通信なので、自分の顔写真を送るのには抵抗があったからでした。あの時の自分は正しかった!) あとGoogle Cloud APIも悪くなかったと思うのですが、若干Face++よりもお高かったので、お見送りになりました。
次回は実際にFace++を用いて自己満サービスを作りたいと思います。

Amazon linuxで/etc/cron.d/配下または/etc/cron.hourly/配下にcronを設定する.

cronの設定方法はいくつかあって、どこにどう設定すればいいのかの、ベストプラクティスはまだよく分かっていません。
いったんpart1として/etc/cron.d/と/etc/cron.hourly/はいかに設定する方法をまとめておきます。

環境

まずはcron化したり作ると作成します。
僕の常套手段として、適当なpythonスクリプトを作ってslackに通知させます。
ログを出力してtail -f でもいいんですが、ログを出力するために画面を2つ出さなきゃいけないのがちょっと面倒くさいので、slackで受動的に確認します。

cron.d配下

cron化させるslack通知pythonスクリプト

import requests
import json

webhook_url = "https://XXXXXXXXXXXXXX"
requests.post(webhook_url, data = json.dumps({
    'text': u'Test', # 投稿するテキスト
    'username': u'me', # 投稿のユーザー名
    'icon_emoji': u':ghost:', # 投稿のプロフィール画像に入れる絵文字
    'link_names': 1, # メンションを有効にする
}))

これは/home/ec2-user/slack.pyにおいておきます。
cronがrootユーザーとして実行させることにするので、実行権限を付与しておきます。
$ sudo chmod 755 /home/ec2-user/slack.py

cron設定

続いて$ sudo vi /etc/cron.d/slack.py

SHELL=/bin/bash
PATH=/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=root
HOME=/

# For details see man 4 crontabs

# Example of job definition:
# .---------------- minute (0 - 59)
# | .------------- hour (0 - 23)
# | | .---------- day of month (1 - 31)
# | | | .------- month (1 - 12) OR jan,feb,mar,apr ...
# | | | | .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat
# | | | | |
# * * * * * user-name command to be executed
  * * * * *  root /usr/bin/python36 /home/ec2-user/slack.py

これだでけOKです。
特にcronのrestartとかいらないです。
これで1分ごとにslack通知が流れてきました~

cron.hourly配下

cron化させるslack通知pythonスクリプト

こちらは同じものを使っていきます。 配置場所も同じく/home/ec2-user/slack.pyです。

import requests
import json

webhook_url = "https://XXXXXXXXXXXXXX"
requests.post(webhook_url, data = json.dumps({
    'text': u'Test', # 投稿するテキスト
    'username': u'me', # 投稿のユーザー名
    'icon_emoji': u':ghost:', # 投稿のプロフィール画像に入れる絵文字
    'link_names': 1, # メンションを有効にする
}))

cron設定

$ sudo vi /etc/cron.hourly/slackを作成します。
ファイルの中身は以下です。

#!/bin/sh

/usr/bin/python36 /home/ec2-user/slack.py

ここで罠なのですが、このファイル自体にも実行権限を与えないと動かないみたいです。
$ sudo chmod 755 /etc/cron.hourly/slack

hourlyなので、1時間に1回なのですが、確認のために最大1時間も待つなんてとんでもないです。
以下より、一時的に設定を変更します。

$ sudo vi /etc/cron.d/0hourly

SHELL=/bin/bash
PATH=/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=root
HOME=/
30 * * * * root run-parts /etc/cron.hourly

今回は**:30のときに実行されるように変更しました。

実際通知が流れてきました。
成功です。

python36で別のホストのmysqlに接続する。

環境

接続元

接続先

準備

MySQLサーバ

事前にDBとテーブルを作っておきます。 ユーザー、パスワードとかは特に作成せず、起動したてのままでOKです。 SQLは適当に作成します。

mysql > CREATE DATABASE testdb;
mysql > USE testdb;
mysql > CREATE TABLE employee(name varchar(20), age int, job varchar(20), salary int);
mysql > INSERT INTO employee (name, age, job, salary) VALUES ('tanaka',20,'engineer',400);
mysql > GRANT ALL PRIVIEGES ON *.* TO root@aaa.aaa.aaa.aaa;

AWS

セキュリティグループをMySQLのport3306を開けておいてください。

python

sudo pip-3.6 install pymysqlが必要です。

実装コード

import pymysql

db = pymysql.connect(
    host='xxx.xxxx.xxx.xxx',
    user='root',
    password='',
    db='testdb',
    charset='utf8',
    cursorclass=pymysql.cursors.DictCursor,
)


cur = db.cursor()
sql = "select * from employee"
cur.execute(sql)
employees = cur.fetchall()
print(employees)
[{'name': 'tanaka', 'age': 20, 'job': 'engineer', 'salary': 400}]

と、listになって返ってくるので、あとは煮るなり焼くなり好きにしてです。

sudoers.dファイルでコマンドのオプションはどこまで制限できるのか

sudoers.dで指定する、許可されるコマンドは、どこまで寛容なのか。 一字一句そのコマンドじゃなきゃいけないのか。 それとも、オプションだとかパスはよしなに指定させてくれるのか。 検証する。

# rootになっておく  
$ sudo su -
# まずは、現在のユーザー一覧を確認
$ ll /home/
合計 2
drwx------. 5 vagrant   vagrant   4096  1月 12 10:52 vagrant

# userを追加
$ useradd test-user

# ユーザー追加されたのを確認
$ ll /home

# パスワードを設定
$ passwd test-user

# test-userにログイン
$ su test-user

# sudoが必要なコマンドを実行してみます
$ yum install -y git
Loaded plugins: fastestmirror
You need to be root to perform this command.

# rootに戻ります
$ exit

# sudoers権限をいじります
$ visudo

# 下記を追加
# test-userに root権限でのyumの使用を許可します。
# /bin/yumじゃなくていいのかな?
# yumのオプションは何でも使えるのかな?
test-user   ALL=(root)     yum


編集を抜けると
visudo: >>> /etc/sudoers: syntax error near line 121 <<<
What now?
Options are:
  (e)dit sudoers file again
  e(x)it without saving changes to sudoers file
  (Q)uit and save changes to sudoers file (DANGER!)

What now? e
と、
Qをやってしまったが最後、二度とsudoを使えなくなってしまいました。
rootにログインすることも叶わなかった。。。
VM立ち上げ直した。。。


$ visudo 
# 以下のように追加
test-user    ALL=(root)      NOPASSWD: /bin/yum
:wqでちゃんと抜けられました。

# test-userにログイン
$ su test-user

# 権限を確かめる
$ sudo yum install -y tig
->OK! yumのコマンドなら全部使えるっぽい。
権限をもう少し絞ってみます。

# rootになる
$ exit

$ visudo
# 以下の設定に変更
test-user    ALL=(root)      NOPASSWD: /bin/yum install -y tig

# test-userになる
$ su test-usre


# 確かめる
$ sudo yum install -y tig
出来ました。

# 他のコマンドは使えない?
$ sudo yum install -y tree
ダメでした。

# どこまで使えるのか
$ visudo
# 次のように設定
test-user    ALL=(root)      NOPASSWD: /bin/yum install
#これでyum installなら何でも使えるかな?

# 試してみます
$ su test-user
$ sudo yum install -y tree
↓
なんかパスワード求められた。。。
なんかダメみたいだ。
なので、方法としては、
完全に決め打ちのコマンドを一つずつ書くか、
オプションは指定しないで、実行ファイルのパスだけを書くか。
のどちらかっぽい。
コマンドの処理の仕方とかもっと深く知れば「はいはい、そりゃそうだ」って言えるようになる気がする。

40秒で支度しな!AWSのEC2にpython36で最速でflask実行環境を作成する

AWSのEC2、amazon linuxです。

$ sudo yum install -y python36
$ sudo pip-3.6 install flask
$ vi ~/hello.py
# 以下の内容で作成作成
from flask import Flask
app = Flask(__name__)
 
@app.route('/')
def hello():
    return 'Hello World!'
 
if __name__ == '__main__':
    app.run(host='0.0.0.0')
$ python36 hello.py
 * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)

localhost:5000をcurlしてみる。

$ curl http://127.0.0.1:5000
127.0.0.1 - - [20/Feb/2018 16:20:52] "GET / HTTP/1.1" 200 -
Hello World!

いい感じです。

これをブラウザから確認したい。
xxx.xxx.xxx.xxx:5000

Hello World!

が表示されました。

当然セキュリティグループから、アクセスしようとしているIPの5000番ポートを開けておく必要がありやす。

どうでしょう。40秒で支度できましたか?
f:id:benzenetarou:20180221014836j:plain

pythonのboto3でのエラーハンドリング

問題

pythonのboto3で以下のようなコードを書いていて、

try:
    hoge()
except:
    print("error")

flake8にdo not use bare except'と怒られた。
pythonのエラーの書き方をちゃんと調べてみると、エラーの種類をexceptで指定しておかないと、エラーメッセージを表示する際に、実際のエラーの原因とは違うエラーメッセージを表示してしまい、バグのもとになるとのことで、かっちり書くことにしてみた。

ここで言うエラーの種類は、ZeroDivisionErrorとかNameErrorとかValueErrorとかだそうだ。

このhoge()で発生しているエラーがなんの種類のエラーなのか調べてみる。

try:
    hoge()
except:
    print(sys.exc_info())

をやってみると、

(<class 'botocore.errorfactory.LoadBalancerNotFoundException'>, LoadBalancerNotFoundException("An error occurred (LoadBalancerNotFound) when calling the DescribeLoadBalancers operation: Loa
d balancers '[fa]' not found",), <traceback object at 0x7f1eda94c708>)

と返ってくる。 なるほどと思い、

try:
    hoge()
except LoadBalancerNotFoundException:
    print("catch!")

と書くと、うまくいかぬ。 except botocore.errorfactory.LoadBalancerNotFoundExceptionとしてみても、だめ。

During handling of the above exception, another exception occurred:
.
.
.
    except botocore.errorfactory.LoadBalancerNotFoundException:
NameError: name 'botocore' is not defined

となる。さらにエラーが呼び出されてしまう。 どうやってエラーの種類を調べればいいのかは、結局分からなかったが、boto3のエラーハンドリングは以下のように書けるようだ。
stack overflowより( amazon web services - Boto3, python and how to handle errors - Stack Overflow

import boto3
from botocore.exceptions import ClientError

try:
    iam = boto3.client('iam')
    user = iam.create_user(UserName='fred')
    print "Created user: %s" % user
except ClientError as e:
    if e.response['Error']['Code'] == 'EntityAlreadyExists':
        print "User already exists"
    else:
        print "Unexpected error: %s" % e

Clientなのかぁ…解決。

知らないふり

ただ、この答えを書いてくれてる人はどうやってこれを探したんだろうか。。。 と、リファレンスも貼ってくれている。

Upgrading to Clients — botocore 1.8.34 documentation

なるほど、こういうときはライブラリのリファレンスのエラーハンドリングの章を参照すればいいのだな!

いやまて、botocoreってなんだ?boto3がラッパーしているライブラリ??
→ boto3のcoreだそうだ。

しかし、boto3のリファレンスから知らないふりして探そうとしてみても、先程のページが見つけられない。
errorとかで検索かけてみても、たくさん引っかかりすぎる。。。
う〜ん、だめだ。
一旦、これは寝かしておこう。。。

sudo rm -rf で削除できないファイルのパーミッション設定

背景

想定するのは、次のようなケース

普通にrootユーザーから削除しようと思ったら、
# rm -rf /tmp/mydir/*
を実行すればいいだけです。 と、いうことは、sudo権限を持っている一般ユーザーならば、
$ sudo rm -rf /tmp/mydir/*
とすればいいのではないか! となるのが、素直な発想。
しかし、どうやらrmに関してはそうもいかないらしい。

最終的には、mydirのパーミッションを777に変更して、
$ sudo rm -rf /tmp/mydir/*
と実行して、その後にまたパーミッションを700に戻すという愚直な方法で解決しました。

以下、動作確認の備忘録です。

環境

MacOS 10.11.6 El Capitan
Vagrant 2.0.1
CentOS 7.2.1511

動作確認

mydirの作成

まずは、mydirを作成します。 rootになります。 sudo su - mydirをはじめとするファイル群をつくります。

$ mkdir /tmp/mydir
$ mkdir /tmp/mydir/aaa /tmp/mydir/bbb
$ touch /tmp/mydir/aaa/c /tmp/mydir/aaa/d

現在のパーミッションを確認します。

$ yum install -y tree
$ tree /tmp/mydir/ -pug
mydir/
├── [drwxr-xr-x root     root    ]  aaa
│   ├── [-rw-r--r-- root     root    ]  c
│   └── [-rw-r--r-- root     root    ]  d
└── [drwxr-xr-x root     root    ]  bbb

パーミッションを変更します。
$ chmod 700 /tmp/mydir/ --recursive
確認します。

$ tree /tmp/mydir/ -pug
/tmp/mydir/
├── [drwx------ root     root    ]  aaa
│   ├── [-rwx------ root     root    ]  c
│   └── [-rwx------ root     root    ]  d
└── [drwx------ root     root    ]  bbb```

期待通り、再帰的にパーミションの変更ができました。 mydirのパーミッションも確認します。

$ ls -l /tmp/
total 0
drwx------ 4 root root 26 Jan 18 04:06 mydir

そもそもこのディレクトリに対して、rootは期待する動作を実行できるのでしょうか。

$ rm -rf /tmp/mydir/*
$ tree /tmp/mydir -pug
/tmp/mydir

0 directories, 0 files

これはOKですね。 もう一度作り直します。

$ mkdir /tmp/mydir/aaa /tmp/mydir/bbb
$ touch /tmp/mydir/aaa/c /tmp/mydir/aaa/d
$ chmod 700 /tmp/mydir/ --recursive

ユーザー作成 そのままvagrantユーザーで行っても挙動は同じだと思いますが、新しく作ってまっさらなアカウントから検証します。

$ useradd test-user
$ passwd test-user

sudo権限を付与する /etc/sudoers.dを最近覚えた!
$ visudo -f /etc/sudoers.d/test-user
以下のように設定
test-user ALL=(ALL) NOPASSWD: ALL
正常に終了できました。

test-userで作業
$ su test-user
早速削除をやってみます。

$ sudo rm -rf /tmp/mydir/*
$ sudo tree /tmp/mydir/ -pug
/tmp/mydir/
├── [drwx------ root     root    ]  aaa
│   ├── [-rwx------ root     root    ]  c
│   └── [-rwx------ root     root    ]  d
└── [drwx------ root     root    ]  bbb

2 directories, 2 files

ガッツリ残っている。 やはり、一般ユーザーからは700のrootがownerのファイルは削除できないようです。

解決策

mydirのパーミッションを変更します。

$ sudo chmod 777 /tmp/mydir/
$ ls -l /tmp/
total 0
drwxrwxrwx 4 root root 26 Jan 18 04:17 mydir

mydirのパーミッションが変更されました。 mydir以下のパーミッションは変わっていません。

$ sudo tree /tmp/mydir/ -pug
/tmp/mydir/
├── [drwx------ root     root    ]  aaa
│   ├── [-rwx------ root     root    ]  c
│   └── [-rwx------ root     root    ]  d
└── [drwx------ root     root    ]  bbb

2 directories, 2 files

と、これでrmを実行してみます。

$ sudo rm -rf /tmp/mydir/*
$ sudo tree /tmp/mydir/ -pug
/tmp/mydir/

0 directories, 0 files

これでめでたく削除できました〜 あとはmydirのパーミッションを戻しておしまいですね
$ sudo chmod 700 /tmp/mydir

本当はソースとか読んで、「はいはい、そういう仕様ね」ってやりたいんですが、それはまたの機会にしておきます。